I have made android audio application.
And now some users reporting that app is not responding on media buttons (play, pause, next) when they connected the device via Bluetooth in the car.
At the same time, I tested app with Bluetooth speaker - play/pause button works as needed.
Are there any differences between Bluetooth speaker and Bluetooth radio in the car??? It is very strange
I'm using ExoPlayer and have this code for handling media buttons:
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null, getApplicationContext(), MediaButtonReceiver.class);
mSession.setMediaButtonReceiver(PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0));
MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mSession, new DefaultPlaybackController(), false, null);
mediaSessionConnector.setQueueNavigator(new QueueNavigatorAdapter() {
#Override
public void onSkipToPrevious(Player player) {
playPrevious();
}
#Override
public void onSkipToNext(Player player) {
playNext();
}
});
mediaSessionConnector.setPlayer(mPlayer, null);
and
mPlayer.prepare(source);
mSession.setActive(true);
mPlayer.setPlayWhenReady(true);
I know about AudioManager.registerMediaButtonEventReceiver() and handling KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE etc. but I was sure mSession.setMediaButtonReceiver && MediaSessionConnector do it for me.
Maybe I'm wrong and it is not enough?
minSdkVersion 21
As I remember, you need to perform something like this:
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode()==25)
based on Bluetooth device you are working with, in this case, Bluetooth radio in the car. You need manual for car radio device.
Related
I'm working on an NFC based app developed in Xamarin for Android.
The app has a 'Connect' button and the NFC scanning process starts only when that button
is tapped.
With proper intents configured, whenever an NFC tag is detected, HandleNewIntent() method gets called and NFC read procedure is followed.
internal void HandleNewIntent(Intent intent)
{
try
{
if (intent.Action == NfcAdapter.ActionTagDiscovered || intent.Action == NfcAdapter.ActionNdefDiscovered)
{
_currentTag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
if (_currentTag != null)
{
Task.Run(() => { ReadDataFromTag(_currentTag); });
}
}
}
catch (Exception ex)
{
//Display error
}
}
In normal cases this works fine. However, if the phone is kept in contact with the NFC tag and then the 'Connect' button is tapped on, then the TagDiscovered intent never gets fired. User has to take the phone away, bring it back in contact with the NFC tag and then only the event gets fired.
I observed the same behaviour with generic NFC apps on play store, and on 2 different Android phones.
Looks like Android keeps the NFC of phone tied up when in contact with NFC tag because of which the intents are not detected. Is there anything to be done to release such links (if any) before initiating new NFC connection?
All NFC is handled by the System NFC service and System App and is normally triggered by a Tag coming in to range and then an Intent being delivered to the right app immediately, therefore the system NFC service has already delivered the Intent to another App before you App has had the button pressed.
You don't show all the code that you do to setup your Apps' NFC configuration but as you are talking about Intents you are probably using the old and unreliable enableForegroundDispatch API which has a number of issues.
While it is more normal to enable foreground NFC detection as soon as you App is resumed and then process the Tag on immediate detection, it is possible to store the Tag object until a button is pressed.
I suggest that you use the better and newer enableReaderMode API that has less of a problem because it is a more direct interface to the NFC hardware and thus you use case seems to work using the code below.
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void Button(View view) {
Log.v("TAG", "DetailedScoresActivity:onTagDiscovered:Start");
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter!= null) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
mNfcAdapter.enableReaderMode(this,
this,
NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
}
}
public void onTagDiscovered(Tag tag) {
Log.v("TAG", "onTagDiscovered:Start");
}
#Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
}
For me you can place a Tag against the phone and then start the App and as soon as the Button is clicked and `enableReaderMode` is enabled then `onTagDiscovered` is triggered with the Tag object.
I have a xamarin project (API 28, soon to be 29), and I need to catch the event of bluetooth a2dp device.
I have a broadcast receiver with the following intent filter:
IntentFilter bluetoothFilter = new IntentFilter();
bluetoothFilter.AddAction(BluetoothDevice.ActionAclConnected);
bluetoothFilter.AddAction(BluetoothDevice.ActionAclDisconnectRequested);
bluetoothFilter.AddAction(BluetoothDevice.ActionAclDisconnected);
var btReciever = new BluetoothReceiver();
this.RegisterReceiver(btReciever, bluetoothFilter);
In my manisfest, I got the following permission:
<uses-permission android:name="android.permission.BLUETOOTH" />
In the Receiver.OnReceive, I got this code:
public override void OnReceive(Context context, Intent intent) {
String action = intent.Action;
BluetoothDevice device = (BluetoothDevice)intent.GetParcelableExtra(BluetoothDevice.ExtraDevice);
There I have a switch:
switch (action)
{
case BluetoothDevice.ActionFound:
Android.Util.Log.Debug(TAG, "Device Found");
//Device found
break;
case BluetoothDevice.ActionAclConnected:
Android.Util.Log.Debug(TAG, "Device Connected");
BluetoothAdapter.DefaultAdapter.GetProfileProxy(context, btsListener, ProfileType.A2dp);
Thread.Sleep(1500);
var manager = context.GetSystemService(Context.AudioService) as AudioManager;
var devices = manager.GetDevices(GetDevicesTargets.Outputs);
Android.Util.Log.Debug(TAG, "devices=" + devices.Length);
foreach (var dev in devices)
{
Android.Util.Log.Debug(TAG, "dev: id={0} name={1} type={2}", dev.Id, dev.ProductName, dev.Type);
if (dev.ProductName == device.Name)
{
if (dev.Type.ToString().Contains("a2dp"))
{
BluetoothAdapter.DefaultAdapter.GetProfileProxy(context, btsListener, ProfileType.A2dp);
}
Android.Util.Log.Debug(TAG, "Found output device");
}
}
//Device is now connected
break;
...
}
And I got a listener that implements the IBluetoothProfileServiceListener interface, and looks like this:
var btsListener = new BTServiceListener();
class BTServiceListener : AppCompatActivity, IBluetoothProfileServiceListener
{
public void OnServiceConnected([GeneratedEnum] ProfileType profile, IBluetoothProfile proxy)
{
if (profile == ProfileType.A2dp)
{
Android.Util.Log.Debug(TAG, "A2dp");
}
}
...
}
I need to catch the event onConnect of the bluetooth a2dp (and later headset), but I have no idea how exactly I should do it.
This code in the receiver, shows the bluetooth onConnect event (BluetoothDevice.ActionAclConnected in the switch), then I check the device list, there is not yet the connected device, then I wait 1500ms (I need somehow to improve this method, this cannot stay like this), for the audioService to add the actual a2dp device to the list, and in the for loop, I find the additional device via its name, and I am certain it is the right one. BUT, I have no programmaticaly way to find out what type of device was connected (remote, headset, a2bp...) other than to check is the name contains a2dp (see for loop)
After my research, I found this line:
BluetoothAdapter.DefaultAdapter.GetProfileProxy(context, btsListener, ProfileType.A2dp);
This uses the context, the listener, and the desierd type of device (see Listener: BTServiceListener ), the problem is, I don't know if the proxy in the listener is the same device as the device in the broadcast receiver onConnect, and I have no idea how to use that function.
So my questions:
How and when should I use the BluetoothAdapter.DefaultAdapter.GetProfileProxy function and be certain that I have the same device in the listener and in the onConnect function?
How to get all the devices from the manager without putting the thread to sleep? Because, without the Thread.Sleep, the actual device is not in the list because the onConnect function is called earlyer then the addition of the device to the audioService.
Thread.Sleep(1500); // <-- this needs to go
var manager = context.GetSystemService(Context.AudioService) as AudioManager;
var devices = manager.GetDevices(GetDevicesTargets.Outputs);
How should I distinguish between the device types? Because, I have a feeling that my method of String.Contains(string) is not the way to go
Sorry for the long question, thank you for your help and time.
Let me know, if you need anything else.
I created App to App Android Sinch Application. When user on call without headphone its working on speaker but if user connect headphone its not automatically connecting to headphone instead of that i need to manually mute speaker and its connecting to headphone. Please check the code i done.
private void enableSpeaker(boolean enable) {
AudioController audioController = getSinchServiceInterface().getAudioController();
if (enable)
audioController.enableSpeaker();
else
audioController.disableSpeaker();
switchVolume.setImageDrawable(ContextCompat.getDrawable(this, isSpeaker ? R.drawable.ic_speaker : R.drawable.ic_speaker_off));
}
private void setMuteUnmute() {
AudioController audioController = getSinchServiceInterface().getAudioController();
if (isMute) {
audioController.mute();
switchMic.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_mic_off));
} else {
audioController.unmute();
switchMic.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_mic_on));
}
}
I think you enabled Speaker in your onCallEstablished method.
Remove enableSpeaker() method from onCallEstablished method and add enableAutomaticAudioRouting() like below.
For more details check sinch documentation Here
#Override
public void onCallEstablished(Call call)
{
AudioController audioController = getSinchServiceInterface().getAudioController();
audioController.enableAutomaticAudioRouting(true, AudioController.UseSpeakerphone.SPEAKERPHONE_AUTO);
}
My requirement is to switch audio between Bluetooth and phone speaker as per user selection.
Below is the code snippet:
//AudioTrack for incoming audio to play as below:
int mMaxJitter = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT);
new AudioTrack(AudioManager.STREAM_VOICE_CALL,8000,
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,
mMaxJitter, AudioTrack.MODE_STREAM);
//To register broadcast receiver for bluetooth audio routing
IntentFilter ifil = new IntentFilter();
ifil.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
this.registerReceiver(<receiver instance>,ifil);
//To get AudioManager service
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
//Whenever user select to route audio to Bluetooth
mAudioManager.setMode(AudioManager.MODE_IN_CALL);//tried setting with other mode also viz. MODE_NORMAL, MODE_IN_COMMUNICATION but no luck
mAudioManager.startBluetoothSco();//after this I get AudioManager.SCO_AUDIO_STATE_CONNECTED state in the receiver
mAudioManager.setBluetoothScoOn(true);
mAudioManager.setSpeakerphoneOn(false);
//Whenever user select to route audio to Phone Speaker
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();//after this I get AudioManager.SCO_AUDIO_STATE_DISCONNECTED state in the receiver
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
Issues:
1. I'm able to route audio but Behavior is inconsistent, sometimes it routes to phone speaker even if user choose to route to bluetooth(bluetooth is connected)
2. If audio is routed to phone speaker, volume becomes low(please don't say check the phone volume)
3. Only a few times I could observe audio routing is proper as per choice, if I repeat it becomes weird as I mentioned above.
Android version: Jellybean 4.3
Has anyone faced something similar behavior ?
Thanks!
I got the reason of inconsistent audio routing, it was because I was setting phone speaker false, also I was using inappropriate mode...
below combination worked for me:
//For BT
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
//For phone ear piece
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(false);
//For phone speaker(loadspeaker)
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
Android version: 4.3
Thanks!
if it still relevant to someone, this is my solution:
(tested on samsung s7 sm-g9307 with android version 6.0.1)
public class AudioSourceUtil {
private static void reset(AudioManager audioManager) {
if (audioManager != null) {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
audioManager.setSpeakerphoneOn(false);
audioManager.setWiredHeadsetOn(false);
}
}
public static void connectEarpiece(AudioManager audioManager) {
reset(audioManager);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
}
public static void connectSpeaker(AudioManager audioManager) {
reset(audioManager);
audioManager.setSpeakerphoneOn(true);
}
public static void connectHeadphones(AudioManager audioManager) {
reset(audioManager);
audioManager.setWiredHeadsetOn(true);
}
public static void connectBluetooth(AudioManager audioManager) {
reset(audioManager);
}
}
And for the usage by clicking a button (tab in tab layout):
/**
* There are 3 scenarios for the audio source:
* 1. No headphones and no bluetooth device: toggle phone/ speaker
* 2. Headphones connected: toggle headphones/ speaker
* 3. Bluetooth connected: toggle phone/ speaker/ bluetooth
*
* #param tab
*/
private void handleTabAudioSourceClick(TabLayout.Tab tab) {
View view = tab.getCustomView();
ImageView icon = (ImageView) view.findViewById(R.id.imageViewIcon);
int currentAudioSourceIdentifier = (Integer) view.getTag();
if (audioManager.isWiredHeadsetOn() == false && BluetoothManager.isBluetoothHeadsetConnected() == false) {
// No headphones and no bluetooth device: toggle phone/ speaker.
if (currentAudioSourceIdentifier == R.drawable.tab_speaker) {
// Current audio source is earpiece, moving to speaker.
view.setTag(android.R.drawable.stat_sys_speakerphone);
icon.setImageResource(android.R.drawable.stat_sys_speakerphone);
AudioSourceUtil.connectSpeaker(audioManager);
} else {
// Current audio source is speaker, moving to earpiece.
view.setTag(R.drawable.tab_speaker);
icon.setImageResource(R.drawable.tab_speaker);
AudioSourceUtil.connectEarpiece(audioManager);
}
} else if (audioManager.isWiredHeadsetOn()) {
// Headphones connected: toggle headphones/ speaker.
if (currentAudioSourceIdentifier == android.R.drawable.stat_sys_speakerphone) {
// Current audio source is speaker, moving to headphones.
view.setTag(android.R.drawable.stat_sys_headset);
icon.setImageResource(android.R.drawable.stat_sys_headset);
AudioSourceUtil.connectHeadphones(audioManager);
} else {
// Current audio source is headphones, moving to speaker.
view.setTag(android.R.drawable.stat_sys_speakerphone);
icon.setImageResource(android.R.drawable.stat_sys_speakerphone);
AudioSourceUtil.connectSpeaker(audioManager);
}
} else if (BluetoothManager.isBluetoothHeadsetConnected()) {
// Bluetooth connected: toggle phone/ speaker/ bluetooth.
if (currentAudioSourceIdentifier == R.drawable.tab_speaker) {
// Current audio source is earpiece, moving to speaker.
view.setTag(android.R.drawable.stat_sys_speakerphone);
icon.setImageResource(android.R.drawable.stat_sys_speakerphone);
AudioSourceUtil.connectSpeaker(audioManager);
} else if (currentAudioSourceIdentifier == android.R.drawable.stat_sys_speakerphone) {
// Current audio source is speaker, moving to bluetooth.
view.setTag(android.R.drawable.stat_sys_data_bluetooth);
icon.setImageResource(android.R.drawable.stat_sys_data_bluetooth);
AudioSourceUtil.connectBluetooth(audioManager);
} else {
// Current audio source is bluetooth, moving to earpiece.
view.setTag(R.drawable.tab_speaker);
icon.setImageResource(R.drawable.tab_speaker);
AudioSourceUtil.connectEarpiece(audioManager);
}
}
}
Use MediaRouter api's for this:
https://developer.android.com/guide/topics/media/mediarouter
It is designed specially for this.
Something like this:
mediaRouter = MediaRouter.getInstance(VideoCallingApp.getContext());
mediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.build();
....
public void onStart() {
mediaRouter.addCallback(mediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
super.onStart();
}
#Override
public void onStop() {
mediaRouter.removeCallback(mMediaRouterCallback);
super.onStop();
}
...and when you want to switch audio device then use mediaRouter.getRoutes() and mediaRouter.selectRoute(route) API's.
I want to know if it is actually possible to react to headphone volume control buttons in an Android application. Whenever I use iPhone/iPod supported headphones, I can use the pause/forward/rewind button with various music apps but NONE of them can react to the volume control.
I've searched a lot and haven't found anything that addresses this issue directly.
I am curious whether this is a limitation on the Android platform or on the apps that I am using. If its not a limitation, how would I capture the volume rocker events from the headphones in my own Android application.
iPhone/iPod seem to be able to react to all headphone events through the 3.5mm jack. It seems to me it should be possible in Android as well.
This is 100% an issue relating to the apps themselves. There are plenty of good examples out there to fix this. For instance, you can solve this issue by:
private MPR mediaPlayerReceiver = new MPR();
onCreate() {
...
IntentFilter mediaFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
// this is nasty, since most apps set it to 9999, so you need to be higher
mediaFilter.setPriority(999);
this.registerReceiver(mediaPlayerReceiver, mediaFilter);
...
}
MPR extends BroadcastReceiver {
public MPR() {
super();
}
#Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
return;
}
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
try {
int action = event.getAction();
switch(action) {
case KeyEvent.ACTION_DOWN :
// handle play/pause here
break;
}
} catch (Exception e) {
// something bad happened
}
abortBroadcast();
}
}