Android API 31 media button detection using MediaSession .setCallback .setMediaButtonBroadcastReceiver and BroadcastReceiver - android

Android API 31 supports at least three different methods claiming to handle media buttons. Despite multiple attempts I have not managed to get any of them to detect a button press on a connected audio headset
Can anyone suggest how to receive button presses on a connected audio headset in Android API 31+?
The API 31+ restriction arises because I am using mediaRecorder to record the audio stream from the headset
The latest three methods I have attempted to use are described below
MediaSession.setCallback is described as "Set the callback to receive updates for the MediaSession. This includes media button events and transport controls."
mediaSession = MediaSession(this, packageName)
mediaSession.setCallback(object : MediaSession.Callback() {
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
Log.d(TAG, "mediaSession.callback ${mediaButtonIntent.action}")
return super.onMediaButtonEvent(mediaButtonIntent)
}
})
MediaSession.setMediaButtonBroadcastReceiver is described as "Set the component name of the manifest-declared android.content.BroadcastReceiver class that should receive media buttons"
val componentName = ComponentName(this, "HeadsetMediaButtonReceiver()")
mediaSession.setMediaButtonBroadcastReceiver(componentName)
With the associated MediaButtonReceiver:
class HeadsetMediaButtonReceiver : MediaButtonReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "HeadsetMediaButtonReceiver() - ${intent?.action}")
}
}
and the receiver field in the Manifest
<receiver android:name=".MainActivity$HeadsetMediaButtonReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
The generic BroadcastReceiver
registerReceiver(HeadsetMediaButtonBroadcastReceiver(), IntentFilter(Intent.ACTION_MEDIA_BUTTON))
To test the above three methods I connect to a Bluetooth headset and use mediaRecorder to record the audio. During the recording I press the headset call button but nothing is logged
The generic BroadcastReceiver is fired using sendBroadcast(), but the two MediaSession button handlers do nothing (even if I only enable one)
findViewById(R.id.send_event_button).setOnClickListener {
sendBroadcast(Intent("android.intent.action.MEDIA_BUTTON"))
}

Related

Send Extra data on Audio Manager object to ensure android.media.RINGER_MODE_CHANGED Action Receiver receives this information

I have a Broadcast Receiver that listens to Ringer mode change action as follows:
public class RingerModeStateChangeReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("RECEIVER", "Ringer Change detected.");
}
}
The manifest declaration is as follows:
<receiver android:name=".receivers.RingerModeStateChangeReceiver" >
<intent-filter>
<action android:name="android.media.RINGER_MODE_CHANGED" />
</intent-filter>
</receiver>
My app changes the Ringer mode by making use of Audio Manager as shown below:
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if(someCondition) {
audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
} else {
audioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
}
Is there a way to detect the ringer change to be coming from Audio Manager(may be by adding some extra content to the audio manager object) in my broadcast receiver?
UPDATE:
I have solved this problem by making use of 2 boolean variables, one representing the updates made to RINGER by some external force and the other representing the updates made by my code. The former is true by default and the later false. The later becomes true only when the update to Ringer is about to be made by my code.
This allows the broadcast receiver to differentiate between external and internal changes made to the RINGER.
Although, this still is a workaround and I am still nowhere close to finding a solution internal to the android system (if at all one does exist).

Accepting a Call via Bluetooth Headset

i am working on a VoIP-Android-App. I would like to accept and decline Calls via a connnected Bluetooth Headset in an Activity.
What I have tried so far:
Using a Media Session to receive Media Button clicks.
Problem: If we start BluetoothSCO we do not receive any Media Button clicks. If we do not start BluetoothSCO we do receive Media Button clicks but we cannot differentiate long and short button clicks because downtime is always 0, the keycode is always KEYCODE_MEDIA_PLAY and the ACTION_DOWN is immediately followed by ACTION_UP. Those problems only occur if we are connected via Bluetooth. If we are connnected over a cable Headset we do get the appropriate keycodes (KEYCODE_HEADSETHOOK) and the downtime is not 0.
Using a BroadcastReceiver to listen for Bluetooth SCO connection changes.
private val scoReceiver = object : BroadcastReceiver() {
fun onReceive(context: Context, intent: Intent) {
val state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)
val previousState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1)
if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED && previousState == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
Log.e(TAG, "SCO Disconnected")
hangupCall()
}
}
}
protected fun onStart() {
super.onStart()
val intentFilter = IntentFilter()
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
registerReceiver(scoReceiver, intentFilter)
}
With this approach i can detect when the user wants to hang up the call, for example a long press on the bluetooth headset because this triggers the SCO to disconnect.
Problem: We can not detect if the user wants to accept an incoming call.
Using dispatchKeyEvent, onKeyDown and onKeyUp.
Problem: They never get called at all.
Does anyone has any advice or a best practice how to correctly handle bluetooth headsets? Any help is very appreciated. Thanks in advance!
During normal and virtual voice call (including ringing) all events of Bluetooth headset unit buttons are processed by Bluetooth Headset Service internaly and not broadcasted as button events. Bluetooth Headset Service redirects these events into Telecom framework (answer/hangupCall).
These events are handled internally in HeadsetStateMachine (under packages/apps/Bluetooth).
These events are forwarded to IBluetoothHeadsetPhone interface. The single application to which all the events are forwarded is defined at run-time by following binding code in HeadsetStateMachine.java. This is to allow phone manufacturers to forward them to custom phone application instead of default one in cases where default one is not used.
Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
intent.setComponent(intent.resolveSystemService(context.getPackageManager(), 0));
if (intent.getComponent() == null || !context.bindService(intent, mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
}
To make the events get forwarded to your application instead of default phone application you would have to modify aosp code.
You would need to intercept the events at one of HeadsetStateMachine , BluetoothHeadsetPhone proxy or the phone application.
Unfortunately what you are looking for is currently not possible without modifying aosp code. Some headsets like Plantronics have custom BT events which are forwarded to all applications - some of the existing VoIP applications support these custom intents to support at-least answering calls for some of the headsets.
You should use android Telecom API and implement android.telecom.ConnectionService and android.telecom.Connection where you should override onAnswer() callback which will be called when you try to answer a call via bluetooth headset.
For more details read docs - https://developer.android.com/guide/topics/connectivity/telecom/selfManaged

Release WAKELOCK when screen is off

I'm making an Android TV and Amazon Fire TV app that uses WAKELOCK to prevent the TV device from going to sleep. What I need to do though is release the WAKELOCK when the screen gets turned off, e.g. when someone presses the power button on the TV, as in this case the Amazon Fire TV Stick etc stay active although the TV is powered off.
I then need to re-add the WAKELOCK when the TV is powered on. What is the accepted best practice for handling this?
EDIT: as per comment I'm updating this response with the most effective method.
In a nutshell you can achieve this in two ways:
Check if the HDMI gets disconnected (mainly works on phones, keep reading for TV)
Check if the audio channel becomes noisy. As per Android documentation (https://developer.android.com/guide/topics/media/mediaplayer.html#noisyintent) you can do something like the following (change with ):
"You can ensure your app stops playing music in these situations by handling the ACTION_AUDIO_BECOMING_NOISY intent, for which you can register a receiver by adding the following to your manifest:
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
This registers the MusicIntentReceiver class as a broadcast receiver for that intent. You should then implement this class:
public class MusicIntentReceiver extends android.content.BroadcastReceiver {
#Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(
android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// signal your service to stop playback
// (via an Intent, for instance)
}
}
}

Control audio playing in background android

Can I provide play / pause / next and previous control for audio playing in background?
my app play stream audio in Asynck class which make the audio play in background.
I want to allow user to control that playing as in iPhone control center or as music player of samsung devices
plz , help find what I'm looking for
I used the code on section Use Hardware Playback Control Keys to Control Your App’s Audio Playback in doc , but nothing work with me
set up the receiver
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
public class RemoteControlReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {
// Handle key press.
}
}
}
}
but registering the RemoteControl is not accepted by code
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
thanks in advance

How can we disable the start of music player in android phone , when headset button is pressed?

In some of the Samsung galaxy mobile, when we press the headset button, the Music player is started. I want to disable this functionality. On pressing the headset button, I want to do another task.
How can I achieve this?
The Intent.ACTION_MEDIA_BUTTON is received only in My application and music player should not receive the inten. So that the music player is not started and I will do what I wanted to do in my application.
bit late but this can be useful for others.
In manifest file set your intent filter priority to "high" (android:priority). I think highest possible value by documentation is 999.
<receiver android:name="media_button">
<intent-filter android:priority="999">
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Then in your receiver code abort broadcast to other intents. You can do this by calling abortBroadcast() function.
public class media_button extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
//your code ...
//abort other actions
this.abortBroadcast();
}
}
}

Categories

Resources