3333import android .bluetooth .BluetoothDevice ;
3434import android .bluetooth .BluetoothHeadset ;
3535import android .bluetooth .BluetoothProfile ;
36+ import android .content .ActivityNotFoundException ;
3637import android .content .BroadcastReceiver ;
3738import android .content .ComponentName ;
3839import android .content .ContentResolver ;
@@ -3607,31 +3608,43 @@ public void unregisterAudioFocusClient(String clientId) {
36073608 // RemoteControl
36083609 //==========================================================================================
36093610 public void dispatchMediaKeyEvent (KeyEvent keyEvent ) {
3610- dispatchMediaKeyEvent (keyEvent , false /*needWakeLock*/ );
3611+ filterMediaKeyEvent (keyEvent , false /*needWakeLock*/ );
36113612 }
36123613
36133614 public void dispatchMediaKeyEventUnderWakelock (KeyEvent keyEvent ) {
3614- dispatchMediaKeyEvent (keyEvent , true /*needWakeLock*/ );
3615+ filterMediaKeyEvent (keyEvent , true /*needWakeLock*/ );
36153616 }
36163617
3617- /**
3618- * Handles the dispatching of the media button events to one of the registered listeners,
3619- * or if there was none, broadcast a ACTION_MEDIA_BUTTON intent to the rest of the system.
3620- */
3621- private void dispatchMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
3618+ private void filterMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
36223619 // sanity check on the incoming key event
36233620 if (!isValidMediaKeyEvent (keyEvent )) {
36243621 Log .e (TAG , "not dispatching invalid media key event " + keyEvent );
36253622 return ;
36263623 }
3627- // event filtering
3624+ // event filtering based on audio mode
36283625 synchronized (mRingingLock ) {
36293626 if (mIsRinging || (getMode () == AudioSystem .MODE_IN_CALL ) ||
36303627 (getMode () == AudioSystem .MODE_IN_COMMUNICATION ) ||
36313628 (getMode () == AudioSystem .MODE_RINGTONE ) ) {
36323629 return ;
36333630 }
36343631 }
3632+ // event filtering based on voice-based interactions
3633+ if (isValidVoiceInputKeyCode (keyEvent .getKeyCode ())) {
3634+ filterVoiceInputKeyEvent (keyEvent , needWakeLock );
3635+ } else {
3636+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3637+ }
3638+ }
3639+
3640+ /**
3641+ * Handles the dispatching of the media button events to one of the registered listeners,
3642+ * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
3643+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
3644+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
3645+ * is dispatched.
3646+ */
3647+ private void dispatchMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
36353648 if (needWakeLock ) {
36363649 mMediaEventWakeLock .acquire ();
36373650 }
@@ -3660,6 +3673,140 @@ private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
36603673 }
36613674 }
36623675
3676+ /**
3677+ * The minimum duration during which a user must press to trigger voice-based interactions
3678+ */
3679+ private final static int MEDIABUTTON_LONG_PRESS_DURATION_MS = 300 ;
3680+ /**
3681+ * The different states of the state machine to handle the launch of voice-based interactions,
3682+ * stored in mVoiceButtonState.
3683+ */
3684+ private final static int VOICEBUTTON_STATE_IDLE = 0 ;
3685+ private final static int VOICEBUTTON_STATE_DOWN = 1 ;
3686+ private final static int VOICEBUTTON_STATE_DOWN_IGNORE_NEW = 2 ;
3687+ /**
3688+ * The different actions after state transitions on mVoiceButtonState.
3689+ */
3690+ private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1 ;
3691+ private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2 ;
3692+ private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3 ;
3693+
3694+ private final Object mVoiceEventLock = new Object ();
3695+ private int mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3696+ private long mVoiceButtonDownTime = 0 ;
3697+
3698+ /**
3699+ * Log an error when an unexpected action is encountered in the state machine to filter
3700+ * key events.
3701+ * @param keyAction the unexpected action of the key event being filtered
3702+ * @param stateName the string corresponding to the state in which the error occurred
3703+ */
3704+ private static void logErrorForKeyAction (int keyAction , String stateName ) {
3705+ Log .e (TAG , "unexpected action "
3706+ + KeyEvent .actionToString (keyAction )
3707+ + " in " + stateName + " state" );
3708+ }
3709+
3710+ /**
3711+ * Filter key events that may be used for voice-based interactions
3712+ * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
3713+ * media buttons that can be used to trigger voice-based interactions.
3714+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
3715+ * is dispatched.
3716+ */
3717+ private void filterVoiceInputKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
3718+ int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS ;
3719+ int keyAction = keyEvent .getAction ();
3720+ synchronized (mVoiceEventLock ) {
3721+ // state machine on mVoiceButtonState
3722+ switch (mVoiceButtonState ) {
3723+
3724+ case VOICEBUTTON_STATE_IDLE :
3725+ if (keyAction == KeyEvent .ACTION_DOWN ) {
3726+ mVoiceButtonDownTime = keyEvent .getDownTime ();
3727+ // valid state transition
3728+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN ;
3729+ } else if (keyAction == KeyEvent .ACTION_UP ) {
3730+ // no state transition
3731+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3732+ } else {
3733+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_IDLE" );
3734+ }
3735+ break ;
3736+
3737+ case VOICEBUTTON_STATE_DOWN :
3738+ if ((keyEvent .getEventTime () - mVoiceButtonDownTime )
3739+ >= MEDIABUTTON_LONG_PRESS_DURATION_MS ) {
3740+ // press was long enough, start voice-based interactions, regardless of
3741+ // whether this was a DOWN or UP key event
3742+ voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT ;
3743+ if (keyAction == KeyEvent .ACTION_UP ) {
3744+ // done tracking the key press, so transition back to idle state
3745+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3746+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3747+ // no need to observe the upcoming key events
3748+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN_IGNORE_NEW ;
3749+ } else {
3750+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN" );
3751+ }
3752+ } else {
3753+ if (keyAction == KeyEvent .ACTION_UP ) {
3754+ // press wasn't long enough, simulate complete key press
3755+ voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS ;
3756+ // not tracking the key press anymore, so transition back to idle state
3757+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3758+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3759+ // no state transition
3760+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3761+ } else {
3762+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN" );
3763+ }
3764+ }
3765+ break ;
3766+
3767+ case VOICEBUTTON_STATE_DOWN_IGNORE_NEW :
3768+ if (keyAction == KeyEvent .ACTION_UP ) {
3769+ // done tracking the key press, so transition back to idle state
3770+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3771+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3772+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3773+ // no state transition: we've already launched voice-based interactions
3774+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3775+ } else {
3776+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN_IGNORE_NEW" );
3777+ }
3778+ break ;
3779+ }
3780+ }//synchronized (mVoiceEventLock)
3781+
3782+ // take action after media button event filtering for voice-based interactions
3783+ switch (voiceButtonAction ) {
3784+ case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS :
3785+ if (DEBUG_RC ) Log .v (TAG , " ignore key event" );
3786+ break ;
3787+ case VOICEBUTTON_ACTION_START_VOICE_INPUT :
3788+ if (DEBUG_RC ) Log .v (TAG , " start voice-based interactions" );
3789+ // then start the voice-based interactions
3790+ startVoiceBasedInteractions (needWakeLock );
3791+ break ;
3792+ case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS :
3793+ if (DEBUG_RC ) Log .v (TAG , " send simulated key event" );
3794+ sendSimulatedMediaButtonEvent (keyEvent , needWakeLock );
3795+ break ;
3796+ }
3797+ }
3798+
3799+ private void sendSimulatedMediaButtonEvent (KeyEvent originalKeyEvent , boolean needWakeLock ) {
3800+ // send DOWN event
3801+ KeyEvent keyEvent = KeyEvent .changeAction (originalKeyEvent , KeyEvent .ACTION_DOWN );
3802+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3803+ // send UP event
3804+ keyEvent = KeyEvent .changeAction (originalKeyEvent , KeyEvent .ACTION_UP );
3805+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3806+
3807+ }
3808+
3809+
36633810 private static boolean isValidMediaKeyEvent (KeyEvent keyEvent ) {
36643811 if (keyEvent == null ) {
36653812 return false ;
@@ -3686,6 +3833,63 @@ private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
36863833 return true ;
36873834 }
36883835
3836+ /**
3837+ * Checks whether the given key code is one that can trigger the launch of voice-based
3838+ * interactions.
3839+ * @param keyCode the key code associated with the key event
3840+ * @return true if the key is one of the supported voice-based interaction triggers
3841+ */
3842+ private static boolean isValidVoiceInputKeyCode (int keyCode ) {
3843+ if (keyCode == KeyEvent .KEYCODE_HEADSETHOOK ) {
3844+ return true ;
3845+ } else {
3846+ return false ;
3847+ }
3848+ }
3849+
3850+ /**
3851+ * Tell the system to start voice-based interactions / voice commands
3852+ */
3853+ private void startVoiceBasedInteractions (boolean needWakeLock ) {
3854+ Intent voiceIntent = new Intent (android .speech .RecognizerIntent .ACTION_WEB_SEARCH );
3855+ if (needWakeLock ) {
3856+ mMediaEventWakeLock .acquire ();
3857+ }
3858+ voiceIntent .setFlags (Intent .FLAG_ACTIVITY_NEW_TASK
3859+ | Intent .FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS );
3860+ try {
3861+ if (mKeyguardManager != null ) {
3862+ // it's ok to start voice-based interactions when:
3863+ // - the device is locked but doesn't require a password to be unlocked
3864+ // - the device is not locked
3865+ if ((mKeyguardManager .isKeyguardLocked () && !mKeyguardManager .isKeyguardSecure ())
3866+ || !mKeyguardManager .isKeyguardLocked ()) {
3867+ mContext .startActivity (voiceIntent );
3868+ }
3869+ }
3870+ } catch (ActivityNotFoundException e ) {
3871+ Log .e (TAG , "Error launching activity for ACTION_WEB_SEARCH: " + e );
3872+ } finally {
3873+ if (needWakeLock ) {
3874+ mMediaEventWakeLock .release ();
3875+ }
3876+ }
3877+ }
3878+
3879+ /**
3880+ * Verify whether it is safe to start voice-based interactions given the state of the system
3881+ * @return false is the Keyguard is locked and secure, true otherwise
3882+ */
3883+ private boolean safeToStartVoiceBasedInteractions () {
3884+ KeyguardManager keyguard =
3885+ (KeyguardManager ) mContext .getSystemService (Context .KEYGUARD_SERVICE );
3886+ if (keyguard == null ) {
3887+ return false ;
3888+ }
3889+
3890+ return true ;
3891+ }
3892+
36893893 private PowerManager .WakeLock mMediaEventWakeLock ;
36903894
36913895 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980 ; //magic number
@@ -3703,7 +3907,14 @@ public void onSendFinished(PendingIntent pendingIntent, Intent intent,
37033907
37043908 BroadcastReceiver mKeyEventDone = new BroadcastReceiver () {
37053909 public void onReceive (Context context , Intent intent ) {
3706- if (intent .getExtras ().containsKey (EXTRA_WAKELOCK_ACQUIRED )) {
3910+ if (intent == null ) {
3911+ return ;
3912+ }
3913+ Bundle extras = intent .getExtras ();
3914+ if (extras == null ) {
3915+ return ;
3916+ }
3917+ if (extras .containsKey (EXTRA_WAKELOCK_ACQUIRED )) {
37073918 mMediaEventWakeLock .release ();
37083919 }
37093920 }
0 commit comments