Manejar clics de auriculares Bluetooth(ACTION_VOICE_COMMAND y ACTION_WEB_SEARCH) en Android
events headset (3)
Estoy desarrollando una aplicación para Android
y quiero que interactúe con los clics de los headset
. Lo estoy probando en un Nexus 5 con Android KitKat 4.4 .
Probé primero con un simple auricular (no inalámbrico). El evento de botón recibido fue KEYCODE_HEADSETHOOK
(79). MEDIA_BUTTON
un receiver
MEDIA_BUTTON
para manejar sus clics:
<receiver android:name="com.example.mytest.SearchActivity$MediaButtonIntentReceiver">
<intent-filter>
<intent-filter android:priority="1000000000">
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</intent-filter>
</receiver>
Esta es la actividad que tiene el receptor:
public class SearchActivity extends Activity {
private AudioManager mAudioManager;
private ComponentName mAudioReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
mAudioReceiver = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
}
@Override
protected void onResume() {
super.onResume();
mAudioManager.registerMediaButtonEventReceiver(mAudioReceiver);
}
@Override
protected void onPause() {
super.onPause();
mAudioManager.unregisterMediaButtonEventReceiver(mAudioReceiver);
}
public static class MediaButtonIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("SA", "ON RECEIVE");
...
abortBroadcast();
}
}
}
Este código funciona con mi auricular con cable solo si el usuario realiza un breve clic . Al hacer un clic largo se abre la Google Voice Search
. También me gustaría capturar clics largos, pero no me importa si no es posible.
Después de eso lo probé con un auricular bluetooth
. Específicamente, estoy usando el auricular Bluetooth Moveteck BH119A (puedes ver una imagen en la parte inferior de esta publicación). Este auricular solo tiene un botón, y si lo presiono, se abre la siguiente " activity
":
También me gustaría capturar este evento de clic si mi Activity
está abierta. ¿Cómo puedo hacerlo? Intenté agregar los siguientes filtros a mi receptor, pero tampoco funciona:
<action android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
<action android:name="android.intent.action.VOICE_COMMAND" />
<action android:name="android.intent.action.CALL_BUTTON" />
También intenté anular onKeyDown
en mi actividad, pero no se activó.
¿Alguien sabe como puedo interceptar esos eventos?
Este es mi auricular bluetooth:
** EDIT **
Siguiendo los consejos de Toaster, revisé todo el registro en busca de los eventos que desencadena mi auricular.
Auricular con cable largo clic
Este es el registro cuando hago clic durante mucho tiempo en mis auriculares con cable (abre Google Voice Search
):
12-10 09:24:36.644: I/MediaFocusControl(740): voice-based interactions: about to use ACTION_WEB_SEARCH
12-10 09:24:36.644: I/ActivityManager(740): START u0 {act=android.speech.action.WEB_SEARCH flg=0x10800000 cmp=com.google.android.googlequicksearchbox/.SearchActivity} from pid 740
12-10 09:24:36.754: I/ActivityManager(740): START u0 {act=android.speech.action.WEB_SEARCH flg=0x10000000 cmp=com.google.android.googlequicksearchbox/com.google.android.launcher.GEL} from pid 10153
12-10 09:24:36.764: I/InputDispatcher(740): Dropping event because there is no focused window or focused application.
12-10 09:24:36.764: I/InputDispatcher(740): Dropping event because there is no focused window or focused application.
12-10 09:24:36.774: I/GEL(1025): handleIntent(Intent { act=android.speech.action.WEB_SEARCH flg=0x10400000 cmp=com.google.android.googlequicksearchbox/com.google.android.launcher.GEL })
12-10 09:24:36.774: V/SearchControllerCache(10153): creating SearchController
12-10 09:24:36.804: I/AudioRouter(10153): ROUTE_NONE->ROUTE_NO_BLUETOOTH
12-10 09:24:36.804: I/MediaFocusControl(740): AudioFocus requestAudioFocus() from android.media.AudioManager@4267ad58com.google.android.voicesearch.audio.AudioRouterImpl$1@42695f60
12-10 09:24:36.804: I/Velvet.SdchManager(10153): Sdch cache load complete.
12-10 09:24:36.814: W/IInputConnectionWrapper(18407): showStatusIcon on inactive InputConnection
12-10 09:24:36.814: I/Icing.InternalIcingCorporaProvider(10153): Updating corpora: A: NONE, C: DELTA
12-10 09:24:36.854: I/VS.G3EngineManager(10153): create_rm: m=GRAMMAR,l=en-US
12-10 09:24:36.854: W/Search.ConcurrentUtils(10153): Executor queue length is now 9. Perhaps some tasks are too long, or the pool is too small. [GrecoExecutor-1]
12-10 09:24:36.854: I/VS.G3EngineManager(10153): Brought up new g3 instance :/system/usr/srec/en-US/grammar.config for: en-USin: 9 ms
12-10 09:24:36.864: D/audio_hw_primary(189): out_set_parameters: enter: usecase(1: low-latency-playback) kvpairs: routing=4
12-10 09:24:36.864: D/audio_hw_primary(189): select_devices: out_snd_device(4: headphones) in_snd_device(0: )
12-10 09:24:36.874: D/audio_hw_primary(189): select_devices: out_snd_device(0: ) in_snd_device(18: headset-mic)
12-10 09:24:36.874: D/(189): Failed to fetch the lookup information of the device 00000008
12-10 09:24:36.874: E/ACDB-LOADER(189): Error: ACDB AudProc vol returned = -19
12-10 09:24:38.864: I/LATENCY(10153): 0-4,45-2064,
12-10 09:24:38.874: I/AudioRouter(10153): ROUTE_NO_BLUETOOTH->ROUTE_NONE
12-10 09:24:38.874: I/MediaFocusControl(740): AudioFocus abandonAudioFocus() from android.media.AudioManager@4267ad58com.google.android.voicesearch.audio.AudioRouterImpl$1@42695f60
12-10 09:24:38.874: I/MicrophoneInputStream(10153): mic_close
Parece que activa un evento ACTION_WEB_SEARCH
, así que intenté agregarlo al filtro. Lo intenté de dos maneras:
Declarando el filtro en el manifiesto:
<action android:name="android.intent.action.WEB_SEARCH" />
Declarando el filtro programáticamente:
protected void onResume() { IntentFilter f = new IntentFilter(Intent.ACTION_WEB_SEARCH); registerReceiver(myReceiver, f); } private BroadcastReceiver myReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d("AA", "ON RECEIVE"); } };
Ninguna de estas opciones funciona. Como dije, este escenario no tiene mucha importancia, puedo manejarlo.
Auricular inalámbrico con un simple clic
El simple clic del auricular inalámbrico es el que abre el marcador de voz y es el evento que realmente necesito capturar . Esta es la salida de registro:
12-10 10:41:22.014: E/bt-rfcomm(21800): PORT_DataInd, p_port:0x7507a7e8, p_data_co_callback is null
12-10 10:41:22.014: D/HeadsetStateMachine(21800): processVrEvent: state=1 mVoiceRecognitionStarted: false mWaitingforVoiceRecognition: false isInCall: false
12-10 10:41:22.014: I/ActivityManager(740): START u0 {act=android.intent.action.VOICE_COMMAND flg=0x10000000 cmp=com.google.android.googlequicksearchbox/com.google.android.voicesearch.handsfree.HandsFreeIntentActivity} from pid 21800
12-10 10:41:22.154: V/Avrcp(21800): New genId = 440, clearing = 1
12-10 10:41:22.154: D/HandsFreeIntentActivity(10153): #onStart(Intent { act=android.intent.action.VOICE_COMMAND flg=0x10800000 cmp=com.google.android.googlequicksearchbox/com.google.android.voicesearch.handsfree.HandsFreeIntentActivity })
12-10 10:41:22.154: D/HandsFreeIntentActivity(10153): Starting activity: Intent { act=android.intent.action.VOICE_COMMAND flg=0x10000000 cmp=com.google.android.googlequicksearchbox/com.google.android.voicesearch.handsfree.HandsFreeActivity }
12-10 10:41:22.154: I/ActivityManager(740): START u0 {act=android.intent.action.VOICE_COMMAND flg=0x10000000 cmp=com.google.android.googlequicksearchbox/com.google.android.voicesearch.handsfree.HandsFreeActivity} from pid 10153
12-10 10:41:22.204: D/OpenGLRenderer(10153): Enabling debug mode 0
12-10 10:41:22.214: W/IInputConnectionWrapper(18895): showStatusIcon on inactive InputConnection
12-10 10:41:22.244: I/ActivityManager(740): Displayed com.google.android.googlequicksearchbox/com.google.android.voicesearch.handsfree.HandsFreeActivity: +80ms (total +89ms)
12-10 10:41:22.374: I/AudioRouter(10153): ROUTE_NONE->ROUTE_BLUETOOTH_WANTED
12-10 10:41:22.384: I/MediaFocusControl(740): AudioFocus requestAudioFocus() from android.media.AudioManager@4267ad58com.google.android.voicesearch.audio.AudioRouterImpl$1@42695f60
12-10 10:41:22.384: V/Avrcp(21800): New genId = 441, clearing = 1
12-10 10:41:22.384: D/BluetoothManagerService(740): Message: 30
12-10 10:41:22.384: D/BluetoothHeadset(10153): Proxy object connected
12-10 10:41:22.384: I/BluetoothController(10153): BT device connected
12-10 10:41:22.394: I/AudioRouter(10153): BT required, starting SCO
12-10 10:41:22.394: I/BluetoothController(10153): Starting VR
12-10 10:41:22.394: D/BluetoothHeadset(10153): startVoiceRecognition()
12-10 10:41:22.394: D/HeadsetStateMachine(21800): Voice recognition started successfully
12-10 10:41:22.394: D/HeadsetStateMachine(21800): Initiating audio connection for Voice Recognition
12-10 10:41:22.394: W/bt-btm(21800): BTM Remote does not support 3-EDR eSCO
12-10 10:41:22.434: I/TextToSpeech(10153): Sucessfully bound to com.google.android.tts
12-10 10:41:22.454: I/TextToSpeech(10153): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
12-10 10:41:22.454: I/TextToSpeech(10153): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
12-10 10:41:22.484: D/dalvikvm(21966): GC_CONCURRENT freed 346K, 3% free 16647K/17064K, paused 2ms+3ms, total 13ms
12-10 10:41:22.764: D/audio_hw_primary(189): out_set_parameters: enter: usecase(1: low-latency-playback) kvpairs: routing=32
12-10 10:41:22.774: D/audio_hw_primary(189): select_devices: out_snd_device(11: bt-sco-headset) in_snd_device(0: )
12-10 10:41:24.874: I/EventLogService(1148): Aggregate from 1386666683008 (log), 1386666683008 (data)
12-10 10:41:24.994: I/ServiceDumpSys(1148): dumping service [account]
12-10 10:41:25.994: D/dalvikvm(10153): GC_CONCURRENT freed 1582K, 15% free 23868K/27920K, paused 5ms+7ms, total 60ms
12-10 10:41:26.014: I/VS.G3EngineManager(10153): create_rm: m=GRAMMAR,l=en-US
12-10 10:41:26.024: I/VS.G3EngineManager(10153): Brought up new g3 instance :/system/usr/srec/en-US/grammar.config for: en-USin: 4 ms
12-10 10:41:26.024: D/audio_hw_primary(189): out_set_parameters: enter: usecase(1: low-latency-playback) kvpairs: routing=32
12-10 10:41:26.034: D/audio_hw_primary(189): select_devices: out_snd_device(0: ) in_snd_device(25: bt-sco-mic)
12-10 10:41:26.034: D/(189): Failed to fetch the lookup information of the device 00000015
12-10 10:41:26.034: E/ACDB-LOADER(189): Error: ACDB AudProc vol returned = -19
Esta vez parece que está enviando un ACTION_VOICE_COMMAND
, así que intenté agregarlo al filtro. Lo intenté de dos maneras:
Declarando el filtro en el manifiesto:
<action android:name="android.intent.action.VOICE_COMMAND" />
Declarando el filtro programáticamente:
protected void onResume() { IntentFilter f = new IntentFilter(Intent.ACTION_VOICE_COMMAND); registerReceiver(myReceiver, f); } private BroadcastReceiver myReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d("AA", "ON RECEIVE"); } };
Una vez más, no estoy recibiendo estos eventos, no sé por qué.
Finalmente logré detectar los eventos. No sabía acerca de esta clase:
http://developer.android.com/reference/android/bluetooth/BluetoothHeadset.html
Utilizando las clases BluetoothAdapter, BluetoothHeadset y BluetoothDevice, puedo registrar un receptor con IntentFilter BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED y puedo detectar clics en mis auriculares.
El problema con esto es que la transmisión no está ordenada, por lo que no puedo cancelarla. Puedo cerrar la actividad de VoiceDialer justo después de que se abra, pero eso no es lo que quiero.
Seguiré luchando con esto.
Gracias @Toaster por tus esfuerzos :)
EDITAR:
Código utilizado para detectar los eventos:
protected BluetoothAdapter mBluetoothAdapter;
protected BluetoothHeadset mBluetoothHeadset;
protected BluetoothDevice mConnectedHeadset;
protected AudioManager mAudioManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null)
{
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager.isBluetoothScoAvailableOffCall())
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
mBluetoothAdapter.getProfileProxy(this, mHeadsetProfileListener, BluetoothProfile.HEADSET);
}
}
}
}
protected BluetoothProfile.ServiceListener mHeadsetProfileListener = new BluetoothProfile.ServiceListener()
{
/**
* This method is never called, even when we closeProfileProxy on onPause.
* When or will it ever be called???
*/
@Override
public void onServiceDisconnected(int profile)
{
mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
unregisterReceiver(mHeadsetBroadcastReceiver);
mBluetoothHeadset = null;
}
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy)
{
// mBluetoothHeadset is just a head set profile,
// it does not represent a head set device.
mBluetoothHeadset = (BluetoothHeadset) proxy;
// If a head set is connected before this application starts,
// ACTION_CONNECTION_STATE_CHANGED will not be broadcast.
// So we need to check for already connected head set.
List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
if (devices.size() > 0)
{
// Only one head set can be connected at a time,
// so the connected head set is at index 0.
mConnectedHeadset = devices.get(0);
String log;
// The audio should not yet be connected at this stage.
// But just to make sure we check.
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "Profile listener audio already connected"; //$NON-NLS-1$
}
else
{
// The if statement is just for debug. So far startVoiceRecognition always
// returns true here. What can we do if it returns false? Perhaps the only
// sensible thing is to inform the user.
// Well actually, it only returns true if a call to stopVoiceRecognition is
// call somewhere after a call to startVoiceRecognition. Otherwise, if
// stopVoiceRecognition is never called, then when the application is restarted
// startVoiceRecognition always returns false whenever it is called.
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "Profile listener startVoiceRecognition returns true"; //$NON-NLS-1$
}
else
{
log = "Profile listener startVoiceRecognition returns false"; //$NON-NLS-1$
}
}
Log.d(TAG, log);
}
// During the active life time of the app, a user may turn on and off the head set.
// So register for broadcast of connection states.
registerReceiver(mHeadsetBroadcastReceiver,
new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
// Calling startVoiceRecognition does not result in immediate audio connection.
// So register for broadcast of audio connection states. This broadcast will
// only be sent if startVoiceRecognition returns true.
IntentFilter f = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
f.setPriority(Integer.MAX_VALUE);
registerReceiver(mHeadsetBroadcastReceiver, f);
}
};
protected BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
int state;
int previousState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, BluetoothHeadset.STATE_DISCONNECTED);
String log = "";
if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED))
{
state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
if (state == BluetoothHeadset.STATE_CONNECTED)
{
mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Audio should not be connected yet but just to make sure.
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "Headset connected audio already connected";
}
else
{
// Calling startVoiceRecognition always returns false here,
// that why a count down timer is implemented to call
// startVoiceRecognition in the onTick and onFinish.
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "Headset connected startVoiceRecognition returns true"; $NON-NLS-1$
}
else
{
log = "Headset connected startVoiceRecognition returns false";
}
}
}
else if (state == BluetoothHeadset.STATE_DISCONNECTED)
{
// Calling stopVoiceRecognition always returns false here
// as it should since the headset is no longer connected.
mConnectedHeadset = null;
}
}
else // audio
{
state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED)
{
log = "Head set audio connected, cancel countdown timer";
}
else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
{
// The headset audio is disconnected, but calling
// stopVoiceRecognition always returns true here.
boolean returnValue = mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
log = "Audio disconnected stopVoiceRecognition return " + returnValue;
}
}
log += "/nAction = " + action + "/nState = " + state
+ " previous state = " + previousState;
Log.d(TAG, log);
}
};
Como dije, puedo detectar los eventos, pero no puedo a bordo de la transmisión.
Para la acción del marcador por voz, agregue lo siguiente a su manifiesto:
<action android:name="android.intent.action.VOICE_COMMAND" />
<category android:name="android.intent.category.DEFAULT" />
Como ACTION_VOICE_COMMAND de tus registros de depuración, ACTION_VOICE_COMMAND es la acción activada, pero sin CATEGORY_DEFAULT , tu aplicación no será considerada. (Lo probé con mis propios auriculares bluetooth, ¡y funcionó para mí!)
Ya que tiene un método que recibe transmisiones, ¿no puede intentar imprimir qué intención recibe al hacer un clic prolongado en sus auriculares, para saber qué evento recibe su teléfono en este caso y manejarlo?
@Override
public void onReceive(Context context, Intent intent) {
Log.d("SA", "ON RECEIVE" + intent.getAction()); // Print the received event
...
abortBroadcast();
}