android events bluetooth headset

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:

  1. Declarando el filtro en el manifiesto:

    <action android:name="android.intent.action.WEB_SEARCH" />

  2. 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:

  1. Declarando el filtro en el manifiesto:

    <action android:name="android.intent.action.VOICE_COMMAND" />

  2. 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(); }