Accepting a Call via Bluetooth Headset - android

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

Related

Does android broadcast an event on completion of Bluetooth File Reception and if Yes, which is the Intent?

I am trying to work with an android broadcast receiver, which i want to be invoked upon completion of BT file transfer (in case of receiving file).
Use Case :
Device (A) is already paired with another Bluetooth enabled device (B) and when B sends a file to device A, I want my broadcast receiver to execute.
Is there any way to achieve it?
I tried filtering for below events, but it seems to be private events of default Bluetooth app and is intented only for BluetoothShare component that pushes the notification on completion of file reception and NOT broadcasted publicly. As a result, my broadcast receiver is NOT invoked real device with actual file transfers but when I send any of the below intent from ADB shell its working in both emulator and real device.
Intents filtered in AndroidManifest.xml:
"android.btopp.intent.extra.**BT_OPP_TRANSFER_STATUS**"
"android.btopp.intent.action.**BT_OPP_TRANSFER_DONE**"
"android.intent.action.**DOWNLOAD_COMPLETE**"
Any leads would be greatly appreciated.
Thanks
You can monitor the Connectivity action of Bluetooth. There is a connecting action when the device begin to receive file and when its compeleted there will be a disconnecting action. You can register for ACTION_ACL_DISCONNECTED and onReceive take some action
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == BluetoothDevice.ACTION_ACL_DISCONNECTED) {
}
}
}
Register receiver as follows:
IntentFileter blutoothFilter= new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
this.registerReceiver(mReceiver, blutoothFilter);

How to monitor all input from a Bluetooth HID gadget

I'm trying to implement an app that (mis-) uses a Bluetooth camera shutter release gadget for a completely different purpose. Here's the gadget in question:
http://www.amazon.co.uk/iPazzPort-Bluetooth-Shutter-Android-Smartphones/dp/B00MRTFB4M
As far as I can determine this uses Bluetooth v3 and is an HID device. It apparently fires the camera app shutter by simulating "volume up" (or maybe "volume down"?). Anyway, it does seem to work quite well, although sometimes you have to press the button twice - I think that maybe the first press reestablishes Bluetooth connection and the second, and subsequent, presses then just work.
I've tested it with two different devices running Android 2.3. I do want to be backwards-compatible to that version of Android.
What I want to do is to monitor all input from this device somehow, so my app can detect when the button has been pressed and then do what it wants to use the device for. (It's a kind of panic alarm system so you can press the button to indicate you need help.)
I don't want to get involved in trying to communicate with the device via Bluetooth. Android is already doing that, and it's working, and what I've read about Bluetooth and the HID protocol makes me want to avoid it if at all possible.)
I've tried overriding onKeyDown() and onKeyUp() and dispatchKeyEvent(). Sometimes they get called, sometimes they don't. And when they get called I'm seeing unexpected keyCodes like 66 (Enter) and 8 ("1").
What I'm asking is, is there some way to monitor all input from this Bluetooth HID device, without having to get involved in Bluetooth HID protocol support?
I never found a real answer to my question as such, but fortunately I found a work-around.
This particular Bluetooth gadget always connects to the paired device, sends some text, and then disconnects. So what I'm doing is creating a BroadcastReceiver to get Bluetooth connection (and disconnect) events, and using that to activate the alarm.
// Class used to receive Bluetooth connection and disconnect events. This checks the action is as
// expected (probably unnecessary) and that a request type has been selected, and sends the
// activation message to OutBack Server if so. (Monitoring the disconnect events is probably
// unnecessary, but is done just in case that saves a situation where a connection has been
// missed, or something.)
public class BluetoothReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context androidContext, Intent androidIntent) {
String actionOrNull = androidIntent.getAction();
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(actionOrNull) ||
BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(actionOrNull)) {
Log.d(TAG, "BluetoothReceiver.onReceive() " + actionOrNull);
if (_btnActivate.isEnabled()) {
sendRequestActivationToServer();
}
}
}
}
...
// Reference to the object used to monitor Bluetooth connections and disconnections
private BluetoothReceiver _bluetoothReceiver = null;
...
// Last lifecycle method called before fragment becomes active. This is apparently the
// recommended place to register a receiver object, and is used to register a receiver object to
// monitor Bluetooth connections and disconnections.
#Override
public void onResume() {
super.onResume();
_bluetoothReceiver = new BluetoothReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
getActivity().registerReceiver(_bluetoothReceiver, intentFilter);
}
...
// First lifecycle method called when a fragment is on its way to being paused or destroyed. This
// is apparently the recommended place to unregister a receiver object, and is used to unregister
// the receiver object that monitors Bluetooth connections and disconnections.
#Override
public void onPause() {
super.onPause();
if (_bluetoothReceiver != null) {
getActivity().unregisterReceiver(_bluetoothReceiver);
_bluetoothReceiver = null;
}
}
This was inspired by this question and answer: How to receive intents for Bluetooth devices working?

How to capture key events from bluetooth headset with android

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

How to Detect Headset Capabilities

I know how to detect whether a headset is plugged in but some headsets (e.g Samsung EHS60ANNBE) come with the PAUSE/PLAY (a.k.a KeyEvent.KEYCODE_HEADSETHOOK) button only, without the PREV/NEXT...
I would like to be able to detect whether the headset currently plugged into the Android device has PREV/NEXT (a.k.a KeyEvent.KEYCODE_MEDIA_PREVIOUS/KeyEvent.KEYCODE_MEDIA_NEXT) or not.
Is this possible?
Are you using a BroadcastReceiver? I'm guessing you are.
from Android Dev:
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
//receive the key press event
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
//check the keypress
if (KeyEvent.**** == event.getKeyCode()) {
// Handle key press.
}
}
}
}
** a link with various keypress codes

How to Stop musicplayer when I detach headphones?

I want to stop the currently running MusicPlayer when the user unplugs the headphones (both wired & bluetooth).
I came across sevral posts where use of:
isWiredHeadsetOn(), isBluetoothA2dpOn()
is suggested.
But Android docs says isWiredHeadsetOn() is deprecated. What alternative should I use?
Thanks
From the docs:
isWiredHeadsetOn() - This method is deprecated. Use only to check is a headset is connected or not.
It sounds like it is still recommended for what you're doing, though the docs are worded poorly
I just read a post where someone was suggesting registering for the ACTION_HEADSET_PLUG broadcast event.
You can apparently get the state of it from: intent.getIntExtra("state", 0));
I use AudioManager.ACTION_AUDIO_BECOMING_NOISY event in my app.
Create your custom broadcast receiver:
private class HeadsetIntentReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context cntx, Intent intent)
{
String action = intent.getAction();
if(action.compareTo(AudioManager.ACTION_AUDIO_BECOMING_NOISY) == 0)
{
}
}
}; /* end HeadsetIntentReceiver */
Then register receiver:
headsetReceiver = new HeadsetIntentReceiver();
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
mParentActivity.registerReceiver(headsetReceiver, mIntentFilter);
Don't forget to unregister it later.
According to the documentation for it, it's deprecated but suggested to be used ONLY for checking to see if a headset is connected or not. Should be fine to use.

Categories

Resources