Merge "Start voice-based interactions from headsethook key" into jb-dev
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index f412b9b..8015203 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -33,6 +33,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -3607,24 +3608,20 @@
// RemoteControl
//==========================================================================================
public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
- dispatchMediaKeyEvent(keyEvent, false /*needWakeLock*/);
+ filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
}
public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
- dispatchMediaKeyEvent(keyEvent, true /*needWakeLock*/);
+ filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
}
- /**
- * Handles the dispatching of the media button events to one of the registered listeners,
- * or if there was none, broadcast a ACTION_MEDIA_BUTTON intent to the rest of the system.
- */
- private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
// sanity check on the incoming key event
if (!isValidMediaKeyEvent(keyEvent)) {
Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
return;
}
- // event filtering
+ // event filtering based on audio mode
synchronized(mRingingLock) {
if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
(getMode() == AudioSystem.MODE_IN_COMMUNICATION) ||
@@ -3632,6 +3629,22 @@
return;
}
}
+ // event filtering based on voice-based interactions
+ if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
+ filterVoiceInputKeyEvent(keyEvent, needWakeLock);
+ } else {
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the media button events to one of the registered listeners,
+ * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
if (needWakeLock) {
mMediaEventWakeLock.acquire();
}
@@ -3660,6 +3673,140 @@
}
}
+ /**
+ * The minimum duration during which a user must press to trigger voice-based interactions
+ */
+ private final static int MEDIABUTTON_LONG_PRESS_DURATION_MS = 300;
+ /**
+ * The different states of the state machine to handle the launch of voice-based interactions,
+ * stored in mVoiceButtonState.
+ */
+ private final static int VOICEBUTTON_STATE_IDLE = 0;
+ private final static int VOICEBUTTON_STATE_DOWN = 1;
+ private final static int VOICEBUTTON_STATE_DOWN_IGNORE_NEW = 2;
+ /**
+ * The different actions after state transitions on mVoiceButtonState.
+ */
+ private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
+ private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
+ private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
+
+ private final Object mVoiceEventLock = new Object();
+ private int mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
+ private long mVoiceButtonDownTime = 0;
+
+ /**
+ * Log an error when an unexpected action is encountered in the state machine to filter
+ * key events.
+ * @param keyAction the unexpected action of the key event being filtered
+ * @param stateName the string corresponding to the state in which the error occurred
+ */
+ private static void logErrorForKeyAction(int keyAction, String stateName) {
+ Log.e(TAG, "unexpected action "
+ + KeyEvent.actionToString(keyAction)
+ + " in " + stateName + " state");
+ }
+
+ /**
+ * Filter key events that may be used for voice-based interactions
+ * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
+ * media buttons that can be used to trigger voice-based interactions.
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
+ int keyAction = keyEvent.getAction();
+ synchronized (mVoiceEventLock) {
+ // state machine on mVoiceButtonState
+ switch (mVoiceButtonState) {
+
+ case VOICEBUTTON_STATE_IDLE:
+ if (keyAction == KeyEvent.ACTION_DOWN) {
+ mVoiceButtonDownTime = keyEvent.getDownTime();
+ // valid state transition
+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN;
+ } else if (keyAction == KeyEvent.ACTION_UP) {
+ // no state transition
+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
+ } else {
+ logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_IDLE");
+ }
+ break;
+
+ case VOICEBUTTON_STATE_DOWN:
+ if ((keyEvent.getEventTime() - mVoiceButtonDownTime)
+ >= MEDIABUTTON_LONG_PRESS_DURATION_MS) {
+ // press was long enough, start voice-based interactions, regardless of
+ // whether this was a DOWN or UP key event
+ voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
+ if (keyAction == KeyEvent.ACTION_UP) {
+ // done tracking the key press, so transition back to idle state
+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
+ } else if (keyAction == KeyEvent.ACTION_DOWN) {
+ // no need to observe the upcoming key events
+ mVoiceButtonState = VOICEBUTTON_STATE_DOWN_IGNORE_NEW;
+ } else {
+ logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN");
+ }
+ } else {
+ if (keyAction == KeyEvent.ACTION_UP) {
+ // press wasn't long enough, simulate complete key press
+ voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
+ // not tracking the key press anymore, so transition back to idle state
+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
+ } else if (keyAction == KeyEvent.ACTION_DOWN) {
+ // no state transition
+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
+ } else {
+ logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN");
+ }
+ }
+ break;
+
+ case VOICEBUTTON_STATE_DOWN_IGNORE_NEW:
+ if (keyAction == KeyEvent.ACTION_UP) {
+ // done tracking the key press, so transition back to idle state
+ mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
+ } else if (keyAction == KeyEvent.ACTION_DOWN) {
+ // no state transition: we've already launched voice-based interactions
+ // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
+ } else {
+ logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN_IGNORE_NEW");
+ }
+ break;
+ }
+ }//synchronized (mVoiceEventLock)
+
+ // take action after media button event filtering for voice-based interactions
+ switch (voiceButtonAction) {
+ case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
+ if (DEBUG_RC) Log.v(TAG, " ignore key event");
+ break;
+ case VOICEBUTTON_ACTION_START_VOICE_INPUT:
+ if (DEBUG_RC) Log.v(TAG, " start voice-based interactions");
+ // then start the voice-based interactions
+ startVoiceBasedInteractions(needWakeLock);
+ break;
+ case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
+ if (DEBUG_RC) Log.v(TAG, " send simulated key event");
+ sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
+ break;
+ }
+ }
+
+ private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
+ // send DOWN event
+ KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+ // send UP event
+ keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+
+ }
+
+
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
if (keyEvent == null) {
return false;
@@ -3686,6 +3833,63 @@
return true;
}
+ /**
+ * Checks whether the given key code is one that can trigger the launch of voice-based
+ * interactions.
+ * @param keyCode the key code associated with the key event
+ * @return true if the key is one of the supported voice-based interaction triggers
+ */
+ private static boolean isValidVoiceInputKeyCode(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Tell the system to start voice-based interactions / voice commands
+ */
+ private void startVoiceBasedInteractions(boolean needWakeLock) {
+ Intent voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ try {
+ if (mKeyguardManager != null) {
+ // it's ok to start voice-based interactions when:
+ // - the device is locked but doesn't require a password to be unlocked
+ // - the device is not locked
+ if ((mKeyguardManager.isKeyguardLocked() && !mKeyguardManager.isKeyguardSecure())
+ || !mKeyguardManager.isKeyguardLocked()) {
+ mContext.startActivity(voiceIntent);
+ }
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Error launching activity for ACTION_WEB_SEARCH: " + e);
+ } finally {
+ if (needWakeLock) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+
+ /**
+ * Verify whether it is safe to start voice-based interactions given the state of the system
+ * @return false is the Keyguard is locked and secure, true otherwise
+ */
+ private boolean safeToStartVoiceBasedInteractions() {
+ KeyguardManager keyguard =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ if (keyguard == null) {
+ return false;
+ }
+
+ return true;
+ }
+
private PowerManager.WakeLock mMediaEventWakeLock;
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
@@ -3703,7 +3907,14 @@
BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (intent.getExtras().containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
+ if (intent == null) {
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
mMediaEventWakeLock.release();
}
}