Skip to content

Commit 2d97a70

Browse files
jmtriviAndroid (Google) Code Review
authored andcommitted
Merge "Start voice-based interactions from headsethook key" into jb-dev
2 parents 59e2a1e + 4294b83 commit 2d97a70

File tree

1 file changed

+220
-9
lines changed

1 file changed

+220
-9
lines changed

media/java/android/media/AudioService.java

Lines changed: 220 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import android.bluetooth.BluetoothDevice;
3434
import android.bluetooth.BluetoothHeadset;
3535
import android.bluetooth.BluetoothProfile;
36+
import android.content.ActivityNotFoundException;
3637
import android.content.BroadcastReceiver;
3738
import android.content.ComponentName;
3839
import 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

Comments
 (0)