Goal: Hook Media Button (from Bluetooth headset) in order to react in app.
I've tried every solution here on SO.
IntentFilter before MainActivity in Platforms->Android:
[IntentFilter(
new[]
{
"android.intent.action.VOICE_COMMAND",
"android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED",
"android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT",
"android.bluetooth.adapter.action.STATE_CHANGED",
"android.intent.action.MEDIA_BUTTON"
},
Categories = new[] { "android.intent.category.DEFAULT", "android.intent.category.BROWSABLE" })]
with IntentFilter in OnCreate():
IntentFilter filter2 = new IntentFilter(Intent.ActionMediaButton);//"android.intent.action.MEDIA_BUTTON"
receiver = new BleIntercepterReceiver();
filter2.Priority = 10000; //this line sets receiver priority
RegisterReceiver(receiver, filter2);
Overriding OnKeyDown in MainActivity:
public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent? e)
{
Page p = Shell.Current.CurrentPage;
return base.OnKeyDown(keyCode, e);
}
RegisterMediaButtonEventReceiver.
SetMediaButtonReceiver with Intent.ActionMediaButton setting the PutExtra correctly.
new MediaSessionCompat ... SetCallback, with flags.
None of these work to capture the Bluetooth button (or any headset activity). However, Spotify responds just fine and plays/pauses music instead.
Only the OnKeyDown override works for the phone's volume up/down, etc (not the headset). (Tested by setting breakpoints).
Related
Is there a way to get notified when the user mutes the incoming call ring signal by pressing the volume button and/or turn to silence?
I have tried all methods I can find, using AudioManager.RINGER_MODE_CHANGE_ACTION, android.media.VOLUME_CHANGE_ACTION, android.media.MASTER_MUTE_CHANGED_ACTION and a few more...
None of them give a clear indication that the ring signal is muted.
My problem is not solved by the "possible duplicate", so I will expand with parts of my code for more help.
In my service:
#Override
public void onCreate() {
super.onCreate();
mVolumeReceiver = new VolumeReceiver();
IntentFilter intentFilter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mVolumeReceiver, intentFilter);
}
private class VolumeReceiver extends BroadcastReceiver {
private final String TAG = VolumeReceiver.class.getSimpleName();
#Override
public void onReceive(Context context, Intent intent) {
final String[] Modes = { "Unknown", "Silent", "Vibrate", "Normal" };
if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
int newMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
Log.i(TAG, "Ringer mode changed to: " + Modes[newMode + 1]);
}
}
}
I am certain that my service is started, since it's doing other things as it should.
I also tried to register a BroadcastReceiver class in my manifest, but that didn't work either.
I do get one Logcat entry telling me mode is changed to "Normal", probably when the receiver is registered. After that, nothing.
I want to listen device power button in an android service. I can’t use key listener since I’m in a service. Currently I’m doing this by registering a broadcast listener with android.intent.action.SCREEN_OFF/android.intent.action.SCREEN_ON filter like below.
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
Receiver mReceiver = new Receiver();
registerReceiver(mReceiver, filter);
Problem
This is not an ideal way to do this since screen status can change without pressing the power button. For an example, when ringing the phone for an in-coming call, it will on the screen. How can I listen exact hardware power button? Or is it not possible?
When device power button press then dispatchKeyEvent method call
/* lock power button */
#Override
public boolean dispatchKeyEvent(#NonNull KeyEvent event)
{
return event.getKeyCode() == KeyEvent.KEYCODE_POWER || blockedKeys.contains(event.getKeyCode()) || super.dispatchKeyEvent(event);
}
Background Info: I need to detect whenever a user presses the play/pause button found on most headsets (KEYCODE_MEDIA_PLAY_PAUSE).
I have it all mostly working using MediaSessions, but when another app starts playing audio, I stop getting callbacks.
It seems like this is because the app that's playing audio created its own MediaSession and Android sends KeyEvents only to the newest MediaSession. To prevent this I create an OnActiveSessionsChangedListener and create a new MediaSession every time it fires.
This does work, but every time I create a new MediaSession, the listener fires again, so I find myself stuck in an inf loop.
My Question: does anyone know how I can do any of the following??:
Prevent other apps from stealing my media button focus
Detect when I've lost media button focus to another app, so I can create a new MediaSession only then, rather then whenever the active
sessions change
Check if I currently already have media button focus so I needlessly create a new MediaSession
What didn't work:
BroadcastReceiver on
AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION didn't work
because apps have to manually trigger that Broadcast, and many apps,
like NPR One do not
AudioManager.OnAudioFocusChangeListener didn't work because it requires I have
audio focus
BroadcastReceiver with max priority on android.intent.action.MEDIA_BUTTON & calling abortBroadcast(), but when other apps were playing audio, my receiver wasn't triggered. Also, other apps can set max priority as well.
My Code:
mMediaSessionManager.addOnActiveSessionsChangedListener(controllers -> {
boolean updateButtonReceiver = false;
// recreate MediaSession if another app handles media buttons
for (MediaController mediaController : controllers) {
if (!TextUtils.equals(getPackageName(), mediaController.getPackageName())) {
if ((mediaController.getFlags() & (MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)) != 0L) {
updateButtonReceiver = true;
}
}
}
if (updateButtonReceiver) {
// using a handler with a delay of about 2 seconds because this listener fires very often.
mAudioFocusHandler.removeCallbacksAndMessages(null);
mAudioFocusHandler.sendEmptyMessageDelayed(0, AUDIO_FOCUS_DELAY_MS);
}
}, ClickAppNotificationListener.getComponentName(this));
Here is the handler that gets triggered:
private final Handler mAudioFocusHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (mShouldBeEnabled) {
updateButtonReceiverEnabled(true);
}
}
};
And finally here is the method that the Handler triggers:
private void updateButtonReceiverEnabled(boolean shouldBeEnabled) {
// clear old session (not sure if this is necessary)
if (mMediaSession != null) {
mMediaSession.setActive(false);
mMediaSession.setFlags(0);
mMediaSession.setCallback(null);
mMediaSession.release();
mMediaSession = null;
}
mMediaSession = new MediaSessionCompat(this, MEDIA_SESSION_TAG);
mMediaSession.setCallback(mMediaButtonCallback);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
mMediaSession.setActive(true);
mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE)
.setState(PlaybackStateCompat.STATE_CONNECTING, 0, 0f)
.build());
if (shouldBeEnabled != mShouldBeEnabled) {
getPackageManager().setComponentEnabledSetting(mMediaButtonComponent,
shouldBeEnabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
mShouldBeEnabled = shouldBeEnabled;
}
Thanks!
if you just want to capture MediaButton you can register a BroadcastReceiver to get Media Button action all the time .
MediaButtonIntentReceiver class :
public class MediaButtonIntentReceiver extends BroadcastReceiver {
public MediaButtonIntentReceiver() {
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;
}
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
// do something
Toast.makeText(context, "BUTTON PRESSED!", Toast.LENGTH_SHORT).show();
}
abortBroadcast();
}
}
add this to manifest.xml:
<receiver android:name=".MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
and register your BroadcastReceiver like this ( in main activity)
IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
MediaButtonIntentReceiver r = new MediaButtonIntentReceiver();
filter.setPriority(1000);
registerReceiver(r, filter);
also look at :
How to capture key events from bluetooth headset with android
How do I intercept button presses on the headset in Android?
The controllers you get in OnActiveSessionsChangedListener is ordered by priority. You only have to create a new MediaSession if you see that your MediaSessionis not the first one in the list.
Note that you might still run into an infinite loop if there is another app contending the media key events using the same approach.
I program in recent years to Android and I wonder something:
How to detect the presence of headphones?
There is a method: isWiredHeadsetOn() but it doesn't work.
I've tried that but it doesn't work:
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
Log.i("am.isWiredHeadsetOn()", am.isWiredHeadsetOn() + "");
if (am.isWiredHeadsetOn()) {
//code
}
Thank you (and sorry if I made spelling mistakes, I am French)
#Phil answer at https://stackoverflow.com/a/19102661/4758255 is a good implementation to detect the headphone.
Here I'm quoting from his answer. Please upvoted his answer not this!
Upvote the answer: https://stackoverflow.com/a/19102661/4758255
I've had an app in the store for three years that monitors both the wired headset and bluetooth state and nobody has ever complained about battery drain. But that is because I am successfully using a single service and broadcast receiver for detecting events from both. Here's the two key classes:
public class HeadsetStateBroadcastReceiver extends BroadcastReceiver {
public static final String[] HEADPHONE_ACTIONS = {
Intent.ACTION_HEADSET_PLUG,
"android.bluetooth.headset.action.STATE_CHANGED",
"android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"
};
#Override
public void onReceive(final Context context, final Intent intent) {
boolean broadcast = false;
// Wired headset monitoring
if (intent.getAction().equals(HEADPHONE_ACTIONS[0]) {
final int state = intent.getIntExtra("state", 0);
AudioPreferences.setWiredHeadphoneState(context, state > 0);
broadcast = true;
}
// Bluetooth monitoring
// Works up to and including Honeycomb
if (intent.getAction().equals(HEADPHONE_ACTIONS[1])) {
int state = intent.getIntExtra("android.bluetooth.headset.extra.STATE", 0);
AudioPreferences.setBluetoothHeadsetState(context, state == 2);
broadcast = true;
}
// Works for Ice Cream Sandwich
if (intent.getAction().equals(HEADPHONE_ACTIONS[2])) {
int state = intent.getIntExtra("android.bluetooth.profile.extra.STATE", 0);
AudioPreferences.setBluetoothHeadsetState(context, state == 2);
broadcast = true;
}
// Used to inform interested activities that the headset state has changed
if (broadcast) {
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("headsetStateChange"));
}
}
}
Here is the service I use to register the broadcast receiver:
public class HeadsetMonitoringService extends Service {
HeadsetStateBroadcastReceiver headsetStateReceiver;
#Override
public void onCreate() {
headsetStateReceiver = new HeadsetStateBroadcastReceiver();
final IntentFilter filter = new IntentFilter();
for (String action: HeadsetStateBroadcastReceiver.HEADPHONE_ACTIONS) {
filter.addAction(action);
}
registerReceiver(headsetStateReceiver, filter);
}
#Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
return START_STICKY;
}
#Override
public void onDestroy() {
unregisterReceiver(headsetStateReceiver);
}
#Override
public IBinder onBind(final Intent intent) {
return null;
}
}
And here is my manifest entry:
<service
android:name=".services.HeadsetMonitoringService"
android:enabled="true"
android:exported="false" >
<intent-filter>
<action android:name="initialiseHeadsetService" />
</intent-filter>
</service>
How it works is as follows:
I use an on boot broadcast receiver to send a start service message to the HeadsetMonitoringService (you don't have to do it this way, you could just do this when your application starts instead). The HeadsetMonitoringService in turn registers an instance of a broadcast listener that listens to all the headset events I am interested in - they are held in the HEADPHONE_ACTIONS array. Because the service is sticky it hangs around - and therefore so does the broadcast listener. But because both the service and the broadcast listener are event driven they do not consume any power until a headset state change occurs. Additionally, because the service is sticky, it will be restarted by the OS if it dies unexpectedly.
Whenever I receive a headset state change event I also fire a local broadcast so that interested activities can check the new state and take action if required.
For completeness, I should point out that I use another class (not shown here), AudioPreferences, to store as preferences both the Bluetooth and wired headset state, which can then be accessed whenever I need to know the headset state.
Your application will need the android.permission.BLUETOOTH permission if you are interested in the state of a Bluetooth headset. If not, just take out the Bluetooth related actions from the HEADPHONE_ACTIONS array and delete the associated if blocks from the onReceive method.
If you are OK with Marshmallow and up the AudioDeviceCallback might be what you are looking for. It works with an AudioManager and tells you when anything connects and disconnects.
AudioManager.isWiredHeadsetOn() appeared to be the right thing to do. According to the Android developer doc :
Checks whether a wired headset is connected or not.
This is not a valid indication that audio playback is actually over the wired headset as audio routing depends on other conditions.
Returns
true if a wired headset is connected. false if otherwise
But :
you have to add the associated permission to your manifest (MODIFY_AUDIO_SETTINGS)
according to this post, it doesn't work well with Bluetooth headset.
Is there any way to listen to the event of volume change on Android, without just taking over the volume buttons?
The only thing I've found that works is here, but it works only after the volume control has disappeared.
Not all devices have volume buttons, and I need to capture the volume changes as soon as they occur, and not after the volume dialog is gone.
Better, you can register a ContentObserver as follows:
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, new ContentObserver(){...} );
Your ContentObserver might look like this:
public class SettingsContentObserver extends ContentObserver {
private AudioManager audioManager;
public SettingsContentObserver(Context context, Handler handler) {
super(handler);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
#Override
public void onChange(boolean selfChange) {
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
Log.d(TAG, "Volume now " + currentVolume);
}
}
When done:
getApplicationContext().getContentResolver().unregisterContentObserver(mContentObserver);
One caution, though - sometimes the notifications seem to be delayed if there are lots of button presses quickly.
ok , for now , what i do is to listen to the volume buttons using onKeyDown (and check for KEYCODE_VOLUME_DOWN,KEYCODE_VOLUME_MUTE,KEYCODE_VOLUME_UP ) , and using a handler i've posted a new runnable that checks the volume level .
also , since some devices have a volume dialog , i've added a listener to when it is being disappeared , according to this link.
Use broadcast receiver VOLUME_CHANGED_ACTION then use AudioManager to obtain current volume.
<receiver android:name="VolumeChangeReceiver" >
<intent-filter>
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
You can use :
registerMediaButtonEventReceiver (ComponentName eventReceiver)
which registers a component to be the sole receiver of MEDIA_BUTTON intents.
// in your activity.
MediaButtonReceiver receiver = new MediaButtonReceiver();
// in onCreate put
registerMediaButtonEventReceiver(receiver);
class MediaButtonReceiver implements BroadcastReceiver {
void onReceive(Intent intent) {
KeyEvent ke = (KeyEvent)intent.getExtra(Intent.EXTRA_KEY_EVENT);
if (ke .getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) {
//action when volume goes down
}
if (ke .getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) {
//action when volume goes up
}
}
}
//In both onStop and onPause put :
unregisterMediaButtonEventReceiver(receiver);
what we are doing here is defining a BroadcastReceiver that deals with ACTION_MEDIA_BUTTON. and use EXTRA_KEY_EVENT which is containing the key event that caused the broadcast to get what was pressed and act upon that.