Android
アプリケーションを開発していて、それがheadset
ボタンのクリックと相互作用するようにしたい。 Nexus 5 with Android KitKat 4.4でテストしています。
私は最初にシンプルなヘッドセット(ワイヤレスではない)で試しました。受信したボタンイベントはKEYCODE_HEADSETHOOK
(79)でした。クリックを処理するためにMEDIA_BUTTON
receiver
を作成しました。
<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>
これは、レシーバーを保持するアクティビティです。
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();
}
}
}
このコードは私の有線ヘッドセットで機能しますユーザーがショートクリックを実行した場合のみ。ロングクリックするとGoogle Voice Search
が開きます。ロングクリックもキャプチャしたいのですが、それができなくてもかまいません。
その後、bluetooth
ヘッドセットでテストしました。具体的には、Moveteck BluetoothヘッドセットBH119Aを使用しています(この投稿の下部に画像が表示されています)。このヘッドセットにはボタンが1つしかないので、それを押すと次の「activity
」が開きます。
Activity
が開いている場合は、このクリックイベントもキャプチャしたいと思います。どうすればいいですか?次のフィルターをレシーバーに追加しようとしましたが、機能しません。
<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" />
また、アクティビティでonKeyDown
をオーバーライドしようとしましたが、トリガーされません。
これらのイベントを傍受する方法を知っている人はいますか?
これは私のBluetoothヘッドセットです:
**編集**
Toasterのアドバイスに従って、ログ全体をチェックして、ヘッドセットがトリガーするイベントを探しました。
有線ヘッドセットのロングクリック
これは、有線ヘッドセットを長押ししたときのログです(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
ACTION_WEB_SEARCH
イベントをトリガーしているようですので、フィルターに追加してみました。私はそれを2つの方法で試しました:
マニフェストでフィルターを宣言する:
<action Android:name="Android.intent.action.WEB_SEARCH" />
プログラムでフィルターを宣言する:
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");
}
};
このオプションはどれも機能しません。私が言ったように、このシナリオはそれほど重要ではありません、私はそれに対処することができます。
ワイヤレスヘッドセットのシンプルクリック
ワイヤレスヘッドセットのシンプルクリックは、ダイヤラを開くものであり、これは私が本当にキャプチャする必要のあるイベントです。これはログ出力です:
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
今回はACTION_VOICE_COMMAND
を送信しているようですので、フィルターに追加してみました。私はそれを2つの方法で試しました:
マニフェストでフィルターを宣言する:
<action Android:name="Android.intent.action.VOICE_COMMAND" />
プログラムでフィルターを宣言する:
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");
}
};
繰り返しますが、私はこれらのイベントを受け取っていません。理由はわかりません。
私はついにイベントを検出することができました。私はこのクラスについて知りませんでした:
http://developer.Android.com/reference/Android/bluetooth/BluetoothHeadset.html
BluetoothAdapter、BluetoothHeadset、BluetoothDeviceの各クラスを使用すると、IntentFilter BluetoothHeadset.ACTION_AUDIO_STATE_CHANGEDを使用してレシーバーを登録でき、ヘッドセットのクリックを検出できます。
これの問題は、ブロードキャストが順序付けられていないため、中止できないことです。 VoiceDialerアクティビティを開いた直後に閉じることはできますが、それは私が望んでいることではありません。
私はこれに苦労し続けます。
@Toasterにご尽力いただきありがとうございます:)
編集:
イベントの検出に使用されるコード:
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);
}
};
私が言ったように、私はイベントを検出することができますが、私は放送に乗ることができません。
音声ダイヤラアクションの場合、マニフェストに次を追加します。
<action Android:name="Android.intent.action.VOICE_COMMAND" />
<category Android:name="Android.intent.category.DEFAULT" />
デバッグログから推測すると、 ACTION_VOICE_COMMAND がトリガーされたアクションですが、 CATEGORY_DEFAULT がないと、アプリは考慮されません。 (私はこれを自分のBluetoothヘッドセットでテストしましたが、うまくいきました!)
ブロードキャストを受信する方法があるので、ヘッドセットを長押ししたときに受信したインテントを印刷して、この場合に電話が受信したイベントを把握して処理することはできませんか?
@Override
public void onReceive(Context context, Intent intent) {
Log.d("SA", "ON RECEIVE" + intent.getAction()); // Print the received event
...
abortBroadcast();
}