My app can be controlled by normal headset. It simply overrides "onKeyDown". But key events from bluetooth headset are not captured - why? Or how to capture bluetooth key events?
the "log cat" shows the following if i press button on headset:
Bluetooth AT recv(3043): AT+VGS=15
AudioPolicyManagerBase(13654): FM radio recording off
AudioService(2261): sendVolumeUpdate, isKeyguardLocked...Not to update Volume Panel.
VolumePanel(2261): change volume by MSG_VOLUME_CHANGED
VolumePanel(2261): onVolumeChanged(streamType: 6, flags: 0)
VolumePanel(2261): Call setChangeSeekbarColor(false)
i also tried to handle media button actions but this isn't working. my idea is a free configurable key mapping: the user chooses "set key" my app hears on all keys (hardware, media buttons, bluetooth headset) then the user presses a key and the event/key code is stored in config.
Summerizing not working Answers:
Volume buttons must be captured by "VOLUME_CHANGED_ACTION". The problem is this intents are broadcasted to other apps and abortBroadcast() doesn't work (it works only for "ordered" Broadcasts). Another problem is that keys on cable headset and on phone trigger onReceive() twice (why?) the bluetooth headset trigger it once.
The next Problem is the 3rd key on Bluetooth headset. It triggers voice-command (s-voice starts on s3), i tried to capture many different intents regarding this but i can't "receive" this button press and don't know why.
At the end i want capture all kinds of buttons and don't want them handled by other apps (like using onKeyDown and returning true).
Add a broadcast listener to MEDIA_BUTTON:
<intent-filter android:priority="<some number>">
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
You should register your broadcast receiver inside your application (not in manifest file). Otherwise Google Music player will catch your broadcast and aboard it.
Your IntentFilter priority should be higher that other media players priorities in your phone)
Add android.permission.BLUETOOTH permission in manifest to support Bluetooth headset
After received you key you have to manually abort the broadcast using abortBroadcast().
However priorities and abortBroadcast() work fine as long as each app only responds while
e.g. something is played.
But several users also expect a "default player" to be launched (or start playing) upon button press, like the default player, so it might happen some app with a higher priority number won't let the intent come through to your app
In the onReceive, you can get the button event with
KeyEvent key = (KeyEvent)
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
key.getKeyAction() tells you whether the button was released or pressed, key.getKeyCode() tells which button is pressed.
If you want to handle single button cable headsets as well, also
regard the key code KEYCODE_HEADSETHOOK
Override the onKeyDown method in any activity and check for
the KeyEvent.KEYCODE_MEDIA_KEYCODE_pressed_key
e.g.
boolean onKeyDown(int keyCode, KeyEvent event) {
AudibleReadyPlayer abc;
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
// code for fast forward
return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
// code for next
return true;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
// code for play/pause
return true;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
// code for previous
return true;
case KeyEvent.KEYCODE_MEDIA_REWIND:
// code for rewind
return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
// code for stop
return true;
}
return false;
}
Volume key integration example
Android - Volume Buttons used in my application
This one may need permission
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
Or you can try slimier implementations over the following link
Android Developer Blog : Handling remote control buttons
Android Tales : Add Headset button support to your Android application
Check out this article. It explains how to implement something similar using media button actions.
I know you've mentioned that you walked this way without success, still give it a try. Point your attention to gotchas related to registering broadcast receiver, setting intent filter priorities and adding permissions (all explained in the article).
Hope this will help.
If you are trying to listen for this from an activity, use onKeyDown() and catch the KEYCODE_MEDIA_PLAY_PAUSE (KEYCODE_MEDIA_PLAY and KEYCODE_MEDIA_PAUSE post ICS) key events.
Also for the broadcast receiver solution to work make sure there arent other apps installed and running in the background that catch these events which might take priority over yours.
Also look at this: Android - registering a headset button click with BroadcastReceiver
Related
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
What I am trying to do is, being able to catch volume up/down button actions on lockscreen on android 4.4.
Google Cast Design Checklist document describes lock screen requirement "Provide access to the volume control via hardware buttons". I tried various ways to handle hardware volume buttons on lock screen but none of them worked.
onKeyDown/dispatchKeyEvent - I tried to override onKeyDown as well as dispatchKeyEvent methods on Activity, but none of these are executed on lockscreen, these only works when my app is focused.
Settings.System.CONTENT_URI/ContentObserver - Registering content observer on main activity's content resolver does catch the system settings change, but that also does not occur on lockscreen.
android.intent.action.MEDIA_BUTTON - Having this filter in manifest, I am able to receive play/pause actions from lockscreen however no volume change event.
android.media.VOLUME_CHANGED_ACTION - Having this filter in manifest, this event is received in my BroadcastReceiver, unfortunately its extra values never get changed on lockscreen. When I keep hitting the volume button, the returned android.media.EXTRA_VOLUME_STREAM_VALUE remains the same (i.e. always 1), even though the CHANGED action is received by my broadcast receiver.
CastVideos-android - The reference android sender app seems to be able to control volume on receiver even when controling from senders lock screen, however even after putting breakpoitns all over the place around Cast.CastApi.setVolume(), these would not get picked. So it seems that the command is being send to a receiver from somewhere I can not locate.
I can also see some other apps being able to catch HW volume keys i.e. Play Music app. So my device surely is capable...
Can anyone suggest any working solution?
You either need to use RemoteControlClient (on pre-lollipop) or MediaSession that is introduced recently. For an example, look at CastCompanionLibrary, VideoCastManager#setUpRemoteControl() method where RCC is registered with the MediaRouter. If using MediaSession, then you need to register MediaSession with MediaRouter.
At the end it appeared I was missing one line of code:
MediaRouter.getInstance(context).addRemoteControlClient(remoteControlClient);
or
MediaRouter.getInstance(activity).setMediaSession(session.getMediaSession());
here is some more code from my implementation for 4.0+:
import android.media.AudioManager;
import android.media.RemoteControlClient;
import android.support.v7.media.MediaRouter;
remoteControlClient = new RemoteControlClient(pendingIntent);
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.registerMediaButtonEventReceiver(receiver);
audioManager.registerRemoteControlClient(remoteControlClient);
MediaRouter.getInstance(context).addRemoteControlClient(remoteControlClient);
and for 5.0+:
import android.media.AudioManager;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v7.media.MediaRouter;
session = new MediaSessionCompat(activity, TAG);
session.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
MediaRouter.getInstance(activity).setMediaSession(session.getMediaSession());
The interesting thing is, there is some black magic that controls the receiver volume internally and your own Cast.CastApi.setVolume() call is not involved at all
Is it feasible to make our Android application completely transparent (as if its not active at all) and work on the other apps?
My Requirement:
First last an app and after some few settings, make it transparent. Once its transparent, the user will not know that this app is active, however, our app should respond to only specific controls.
This is because of the Broadcast receiver limitation, I will have to use the Volume button for some actions in my application. But, this button doesn't broadcast. So, currently I am using Power button which is not the requirement.
Please throw some light on this. I did some research but, couldnt find any. :(
This is because of the Broadcast receiver limitation, I will have to use the Volume button for some actions in my application. But, this button doesn't broadcast.
I am not sure it this is right. If you read Android BroadCastReceiver for volume key up and down question, it seems that you can detect it in BroadCastRceiver. I've never tried but it might be worth a try. Do something like following:
In your BroadcastReceiver onReceive function, do something like following:
public void onReceive(Context arg0, Intent intent) {
if (intent!=null){
int volume = (Integer)intent.getExtras().get("android.media.EXTRA_VOLUME_STREAM_VALUE");
// Get the old volume from the SharedPOreferences
// volume variable contains the current volume
// Compare it to the old value saved. If it is greater than old value then user pressed the UP Volume button else DOWN volume.
}
}
Also I am not sure that you can make it transparent and still keep it running. You can have a background of an activity as transparent though. How do I create a transparent Activity on Android?. Hope it helps.
Alright as I have been asking the last couple days and inching closer and closer to the final outcome of this question:
Is it possible to use the volume (up / down) buttons to start an app?
Here is the code I am working with:
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
Log.w("myApp", "LONG PRESS");
}
//my code here
return super.onKeyLongPress(keyCode, event);
}
I am wondering what I am doing wrong. Nothing in the code is giving me errors, its just not running when I tell it to.
Note:
I am testing this on a live android if that helps out at all.
Any advice would be wonderful.
Is it possible to use the volume (up / down) buttons to start an app?
No, sorry. You cannot use hardware buttons to start apps, with the exception of the CAMERA button (where it exists) or the MEDIA button (where it exists, typically on headsets). For those, you would register a BroadcastReceiver in the manifest for their respective broadcasts, and bear in mind that those broadcasts are only sent out if the foreground activity does not consume the key event (e.g., music player pausing when the MEDIA button is pressed).
I need catch long press of volume button when phone is sleeping(screen off) and I know this code:
#Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
{
// to your stuff here
return true;
}
return super.onKeyLongPress(keyCode, event);
}
is not helpful(it works only in active intent
I'm curious about this too.
While this isn't necessarily an answer, I have done some research on Services (to catch the volume press I'm guessing) and BroadcastReceivers (onReceive() would receive an intent for ACTION_SCREEN_OFF and probably set a flag for the screen being off). My thinking is starting the service when the screen turns off and kill it when the screen turns on, but I don't know how to join onKeyLongPress and the service.
The sequence I see happening is this: Screen turns off -> BroadcastReceiver receives this command and starts the service to watch for volume press -> receive volume press and do your logic -> kill the service if the screen turns on.
I found a site that apparently handles screen off/on and has an example for an activity and a service but I can't get something together yet:
http://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
One thing I'm worried about when using a service is battery life and processing time but that will be testing down the road. I will hopefully be able to mitigate that with killing the service when the screen turns on. I'll try to keep this entry updated as I make progress.
Good luck!