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 ;
@@ -3575,31 +3576,43 @@ public void unregisterAudioFocusClient(String clientId) {
35753576 // RemoteControl
35763577 //==========================================================================================
35773578 public void dispatchMediaKeyEvent (KeyEvent keyEvent ) {
3578- dispatchMediaKeyEvent (keyEvent , false /*needWakeLock*/ );
3579+ filterMediaKeyEvent (keyEvent , false /*needWakeLock*/ );
35793580 }
35803581
35813582 public void dispatchMediaKeyEventUnderWakelock (KeyEvent keyEvent ) {
3582- dispatchMediaKeyEvent (keyEvent , true /*needWakeLock*/ );
3583+ filterMediaKeyEvent (keyEvent , true /*needWakeLock*/ );
35833584 }
35843585
3585- /**
3586- * Handles the dispatching of the media button events to one of the registered listeners,
3587- * or if there was none, broadcast a ACTION_MEDIA_BUTTON intent to the rest of the system.
3588- */
3589- private void dispatchMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
3586+ private void filterMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
35903587 // sanity check on the incoming key event
35913588 if (!isValidMediaKeyEvent (keyEvent )) {
35923589 Log .e (TAG , "not dispatching invalid media key event " + keyEvent );
35933590 return ;
35943591 }
3595- // event filtering
3592+ // event filtering based on audio mode
35963593 synchronized (mRingingLock ) {
35973594 if (mIsRinging || (getMode () == AudioSystem .MODE_IN_CALL ) ||
35983595 (getMode () == AudioSystem .MODE_IN_COMMUNICATION ) ||
35993596 (getMode () == AudioSystem .MODE_RINGTONE ) ) {
36003597 return ;
36013598 }
36023599 }
3600+ // event filtering based on voice-based interactions
3601+ if (isValidVoiceInputKeyCode (keyEvent .getKeyCode ())) {
3602+ filterVoiceInputKeyEvent (keyEvent , needWakeLock );
3603+ } else {
3604+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3605+ }
3606+ }
3607+
3608+ /**
3609+ * Handles the dispatching of the media button events to one of the registered listeners,
3610+ * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
3611+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
3612+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
3613+ * is dispatched.
3614+ */
3615+ private void dispatchMediaKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
36033616 if (needWakeLock ) {
36043617 mMediaEventWakeLock .acquire ();
36053618 }
@@ -3628,6 +3641,140 @@ private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
36283641 }
36293642 }
36303643
3644+ /**
3645+ * The minimum duration during which a user must press to trigger voice-based interactions
3646+ */
3647+ private final static int MEDIABUTTON_LONG_PRESS_DURATION_MS = 300 ;
3648+ /**
3649+ * The different states of the state machine to handle the launch of voice-based interactions,
3650+ * stored in mVoiceButtonState.
3651+ */
3652+ private final static int VOICEBUTTON_STATE_IDLE = 0 ;
3653+ private final static int VOICEBUTTON_STATE_DOWN = 1 ;
3654+ private final static int VOICEBUTTON_STATE_DOWN_IGNORE_NEW = 2 ;
3655+ /**
3656+ * The different actions after state transitions on mVoiceButtonState.
3657+ */
3658+ private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1 ;
3659+ private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2 ;
3660+ private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3 ;
3661+
3662+ private final Object mVoiceEventLock = new Object ();
3663+ private int mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3664+ private long mVoiceButtonDownTime = 0 ;
3665+
3666+ /**
3667+ * Log an error when an unexpected action is encountered in the state machine to filter
3668+ * key events.
3669+ * @param keyAction the unexpected action of the key event being filtered
3670+ * @param stateName the string corresponding to the state in which the error occurred
3671+ */
3672+ private static void logErrorForKeyAction (int keyAction , String stateName ) {
3673+ Log .e (TAG , "unexpected action "
3674+ + KeyEvent .actionToString (keyAction )
3675+ + " in " + stateName + " state" );
3676+ }
3677+
3678+ /**
3679+ * Filter key events that may be used for voice-based interactions
3680+ * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
3681+ * media buttons that can be used to trigger voice-based interactions.
3682+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
3683+ * is dispatched.
3684+ */
3685+ private void filterVoiceInputKeyEvent (KeyEvent keyEvent , boolean needWakeLock ) {
3686+ int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS ;
3687+ int keyAction = keyEvent .getAction ();
3688+ synchronized (mVoiceEventLock ) {
3689+ // state machine on mVoiceButtonState
3690+ switch (mVoiceButtonState ) {
3691+
3692+ case VOICEBUTTON_STATE_IDLE :
3693+ if (keyAction == KeyEvent .ACTION_DOWN ) {
3694+ mVoiceButtonDownTime = keyEvent .getDownTime ();
3695+ // valid state transition
3696+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN ;
3697+ } else if (keyAction == KeyEvent .ACTION_UP ) {
3698+ // no state transition
3699+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3700+ } else {
3701+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_IDLE" );
3702+ }
3703+ break ;
3704+
3705+ case VOICEBUTTON_STATE_DOWN :
3706+ if ((keyEvent .getEventTime () - mVoiceButtonDownTime )
3707+ >= MEDIABUTTON_LONG_PRESS_DURATION_MS ) {
3708+ // press was long enough, start voice-based interactions, regardless of
3709+ // whether this was a DOWN or UP key event
3710+ voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT ;
3711+ if (keyAction == KeyEvent .ACTION_UP ) {
3712+ // done tracking the key press, so transition back to idle state
3713+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3714+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3715+ // no need to observe the upcoming key events
3716+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN_IGNORE_NEW ;
3717+ } else {
3718+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN" );
3719+ }
3720+ } else {
3721+ if (keyAction == KeyEvent .ACTION_UP ) {
3722+ // press wasn't long enough, simulate complete key press
3723+ voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS ;
3724+ // not tracking the key press anymore, so transition back to idle state
3725+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3726+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3727+ // no state transition
3728+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3729+ } else {
3730+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN" );
3731+ }
3732+ }
3733+ break ;
3734+
3735+ case VOICEBUTTON_STATE_DOWN_IGNORE_NEW :
3736+ if (keyAction == KeyEvent .ACTION_UP ) {
3737+ // done tracking the key press, so transition back to idle state
3738+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE ;
3739+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3740+ } else if (keyAction == KeyEvent .ACTION_DOWN ) {
3741+ // no state transition: we've already launched voice-based interactions
3742+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
3743+ } else {
3744+ logErrorForKeyAction (keyAction , "VOICEBUTTON_STATE_DOWN_IGNORE_NEW" );
3745+ }
3746+ break ;
3747+ }
3748+ }//synchronized (mVoiceEventLock)
3749+
3750+ // take action after media button event filtering for voice-based interactions
3751+ switch (voiceButtonAction ) {
3752+ case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS :
3753+ if (DEBUG_RC ) Log .v (TAG , " ignore key event" );
3754+ break ;
3755+ case VOICEBUTTON_ACTION_START_VOICE_INPUT :
3756+ if (DEBUG_RC ) Log .v (TAG , " start voice-based interactions" );
3757+ // then start the voice-based interactions
3758+ startVoiceBasedInteractions (needWakeLock );
3759+ break ;
3760+ case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS :
3761+ if (DEBUG_RC ) Log .v (TAG , " send simulated key event" );
3762+ sendSimulatedMediaButtonEvent (keyEvent , needWakeLock );
3763+ break ;
3764+ }
3765+ }
3766+
3767+ private void sendSimulatedMediaButtonEvent (KeyEvent originalKeyEvent , boolean needWakeLock ) {
3768+ // send DOWN event
3769+ KeyEvent keyEvent = KeyEvent .changeAction (originalKeyEvent , KeyEvent .ACTION_DOWN );
3770+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3771+ // send UP event
3772+ keyEvent = KeyEvent .changeAction (originalKeyEvent , KeyEvent .ACTION_UP );
3773+ dispatchMediaKeyEvent (keyEvent , needWakeLock );
3774+
3775+ }
3776+
3777+
36313778 private static boolean isValidMediaKeyEvent (KeyEvent keyEvent ) {
36323779 if (keyEvent == null ) {
36333780 return false ;
@@ -3654,6 +3801,63 @@ private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
36543801 return true ;
36553802 }
36563803
3804+ /**
3805+ * Checks whether the given key code is one that can trigger the launch of voice-based
3806+ * interactions.
3807+ * @param keyCode the key code associated with the key event
3808+ * @return true if the key is one of the supported voice-based interaction triggers
3809+ */
3810+ private static boolean isValidVoiceInputKeyCode (int keyCode ) {
3811+ if (keyCode == KeyEvent .KEYCODE_HEADSETHOOK ) {
3812+ return true ;
3813+ } else {
3814+ return false ;
3815+ }
3816+ }
3817+
3818+ /**
3819+ * Tell the system to start voice-based interactions / voice commands
3820+ */
3821+ private void startVoiceBasedInteractions (boolean needWakeLock ) {
3822+ Intent voiceIntent = new Intent (android .speech .RecognizerIntent .ACTION_WEB_SEARCH );
3823+ if (needWakeLock ) {
3824+ mMediaEventWakeLock .acquire ();
3825+ }
3826+ voiceIntent .setFlags (Intent .FLAG_ACTIVITY_NEW_TASK
3827+ | Intent .FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS );
3828+ try {
3829+ if (mKeyguardManager != null ) {
3830+ // it's ok to start voice-based interactions when:
3831+ // - the device is locked but doesn't require a password to be unlocked
3832+ // - the device is not locked
3833+ if ((mKeyguardManager .isKeyguardLocked () && !mKeyguardManager .isKeyguardSecure ())
3834+ || !mKeyguardManager .isKeyguardLocked ()) {
3835+ mContext .startActivity (voiceIntent );
3836+ }
3837+ }
3838+ } catch (ActivityNotFoundException e ) {
3839+ Log .e (TAG , "Error launching activity for ACTION_WEB_SEARCH: " + e );
3840+ } finally {
3841+ if (needWakeLock ) {
3842+ mMediaEventWakeLock .release ();
3843+ }
3844+ }
3845+ }
3846+
3847+ /**
3848+ * Verify whether it is safe to start voice-based interactions given the state of the system
3849+ * @return false is the Keyguard is locked and secure, true otherwise
3850+ */
3851+ private boolean safeToStartVoiceBasedInteractions () {
3852+ KeyguardManager keyguard =
3853+ (KeyguardManager ) mContext .getSystemService (Context .KEYGUARD_SERVICE );
3854+ if (keyguard == null ) {
3855+ return false ;
3856+ }
3857+
3858+ return true ;
3859+ }
3860+
36573861 private PowerManager .WakeLock mMediaEventWakeLock ;
36583862
36593863 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980 ; //magic number
@@ -3671,7 +3875,14 @@ public void onSendFinished(PendingIntent pendingIntent, Intent intent,
36713875
36723876 BroadcastReceiver mKeyEventDone = new BroadcastReceiver () {
36733877 public void onReceive (Context context , Intent intent ) {
3674- if (intent .getExtras ().containsKey (EXTRA_WAKELOCK_ACQUIRED )) {
3878+ if (intent == null ) {
3879+ return ;
3880+ }
3881+ Bundle extras = intent .getExtras ();
3882+ if (extras == null ) {
3883+ return ;
3884+ }
3885+ if (extras .containsKey (EXTRA_WAKELOCK_ACQUIRED )) {
36753886 mMediaEventWakeLock .release ();
36763887 }
36773888 }
0 commit comments