Handle headsethook voice launching
This launches voice search when headsethook is long pressed unless
you're in a call. The handling is done in MediaSessionService. This
CL also adds a switch to the new APIs to the fallback event handler
which was missed previously.
Change-Id: I1cbdff1d6e9f5293885dd8aaed8ba13cb15b36d4
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9a1c6d5..9d85167 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,8 @@
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -40,6 +42,7 @@
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.provider.Settings;
+import android.speech.RecognizerIntent;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -75,6 +78,8 @@
private final Handler mHandler = new Handler();
private final PowerManager.WakeLock mMediaEventWakeLock;
+ private KeyguardManager mKeyguardManager;
+
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
@@ -98,6 +103,8 @@
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
Watchdog.getInstance().addMonitor(this);
updateUser();
+ mKeyguardManager =
+ (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
}
/**
@@ -601,6 +608,9 @@
"android.media.AudioService.WAKELOCK_ACQUIRED";
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
+ private boolean mVoiceButtonDown = false;
+ private boolean mVoiceButtonHandled = false;
+
@Override
public ISession createSession(String packageName, ISessionCallback cb, String tag,
int userId) throws RemoteException {
@@ -679,36 +689,12 @@
try {
synchronized (mLock) {
- MediaSessionRecord mbSession = mPriorityStack
+ MediaSessionRecord session = mPriorityStack
.getDefaultMediaButtonSession(mCurrentUserId);
- if (mbSession != null) {
- if (DEBUG) {
- Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo());
- }
- if (needWakeLock) {
- mKeyEventReceiver.aquireWakeLockLocked();
- }
- // If we don't need a wakelock use -1 as the id so we
- // won't release it later
- mbSession.sendMediaButton(keyEvent,
- needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mKeyEventReceiver);
+ if (isVoiceKey(keyEvent.getKeyCode())) {
+ handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
} else {
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- if (DEBUG) {
- Log.d(TAG, "Sending media key ordered broadcast");
- }
- // Fallback to legacy behavior
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- if (needWakeLock) {
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
- WAKELOCK_RELEASE_ON_FINISHED);
- }
- getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
}
}
} finally {
@@ -751,6 +737,110 @@
}
}
+ private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
+ MediaSessionRecord session) {
+ if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
+ // If the phone app has priority just give it the event
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+ return;
+ }
+ int action = keyEvent.getAction();
+ boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
+ if (action == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ mVoiceButtonDown = true;
+ mVoiceButtonHandled = false;
+ } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
+ mVoiceButtonHandled = true;
+ startVoiceInput(needWakeLock);
+ }
+ } else if (action == KeyEvent.ACTION_UP) {
+ if (mVoiceButtonDown) {
+ mVoiceButtonDown = false;
+ if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+ // Resend the down then send this event through
+ KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
+ dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+ }
+ }
+ }
+ }
+
+ private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
+ MediaSessionRecord session) {
+ if (session != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Sending media key to " + session.getSessionInfo());
+ }
+ if (needWakeLock) {
+ mKeyEventReceiver.aquireWakeLockLocked();
+ }
+ // If we don't need a wakelock use -1 as the id so we
+ // won't release it later
+ session.sendMediaButton(keyEvent,
+ needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+ mKeyEventReceiver);
+ } else {
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Sending media key ordered broadcast");
+ }
+ // Fallback to legacy behavior
+ Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ if (needWakeLock) {
+ keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
+ WAKELOCK_RELEASE_ON_FINISHED);
+ }
+ getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
+ null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
+ }
+ }
+
+ private void startVoiceInput(boolean needWakeLock) {
+ Intent voiceIntent = null;
+ // select which type of search to launch:
+ // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+ // - device locked or screen off: action is
+ // ACTION_VOICE_SEARCH_HANDS_FREE
+ // with EXTRA_SECURE set to true if the device is securely locked
+ PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+ boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ if (!isLocked && pm.isScreenOn()) {
+ voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+ isLocked && mKeyguardManager.isKeyguardSecure());
+ Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
+ }
+ // start the search activity
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ try {
+ if (voiceIntent != null) {
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "No activity for search: " + e);
+ } finally {
+ if (needWakeLock) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+
+ private boolean isVoiceKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
+ }
+
private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {