diff options
6 files changed, 216 insertions, 60 deletions
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 7b0412e..103c3f1 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -28,7 +28,7 @@ import android.os.ResultReceiver; */ oneway interface ISessionCallback { void onCommand(String command, in Bundle extras, in ResultReceiver cb); - void onMediaButton(in Intent mediaButtonIntent, in ResultReceiver cb); + void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb); void onRequestRouteChange(in RouteInfo route); void onRouteConnected(in RouteInfo route, in RouteOptions options); void onRouteDisconnected(in RouteInfo route, int reason); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 5b9adaa..6a62dc2 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -119,13 +119,6 @@ public final class MediaSession { */ public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5; - /** - * Status code indicating the call was handled. - * - * @hide - */ - public static final int RESULT_SUCCESS = 0; - private static final int MSG_MEDIA_BUTTON = 1; private static final int MSG_COMMAND = 2; private static final int MSG_ROUTE_CHANGE = 3; @@ -563,14 +556,17 @@ public final class MediaSession { } @Override - public void onMediaButton(Intent mediaButtonIntent, ResultReceiver cb) + public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) throws RemoteException { MediaSession session = mMediaSession.get(); - if (session != null) { - session.postMediaButton(mediaButtonIntent); - } - if (cb != null) { - cb.send(RESULT_SUCCESS, null); + try { + if (session != null) { + session.postMediaButton(mediaButtonIntent); + } + } finally { + if (cb != null) { + cb.send(sequenceNumber, null); + } } } diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 2e02a66..249b9c4 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -137,6 +137,8 @@ public class MediaSessionLegacyHelper { return; } holder.mMediaButtonListener = new MediaButtonListener(pi, context); + // TODO determine if handling transport performer commands should also + // set this flag holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler); diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java index 417527c..b2ecb61 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java +++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.media.AudioManager; import android.media.IAudioService; +import android.media.session.MediaSessionLegacyHelper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -39,6 +40,9 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { private static String TAG = "PhoneFallbackEventHandler"; private static final boolean DEBUG = false; + // Use the new sessions APIs + private static final boolean USE_SESSIONS = true; + Context mContext; View mView; @@ -70,14 +74,14 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { return onKeyUp(keyCode, event); } } - + boolean onKeyDown(int keyCode, KeyEvent event) { /* **************************************************************************** * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. * See the comment in PhoneWindow.onKeyDown * ****************************************************************************/ final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState(); - + switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: @@ -156,7 +160,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { if (event.getRepeatCount() == 0) { dispatcher.startTracking(event, this); } else if (event.isLongPress() && dispatcher.isTracking(event)) { - Configuration config = mContext.getResources().getConfiguration(); + Configuration config = mContext.getResources().getConfiguration(); if (config.keyboard == Configuration.KEYBOARD_NOKEYS || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { // launch the search activity @@ -191,7 +195,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { if (dispatcher != null) { dispatcher.handleUpEvent(event); } - + switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: @@ -277,29 +281,33 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { } return mKeyguardManager; } - + AudioManager getAudioManager() { if (mAudioManager == null) { mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); } return mAudioManager; } - + void sendCloseSystemWindows() { PhoneWindowManager.sendCloseSystemWindows(mContext, null); } private void handleMediaKeyEvent(KeyEvent keyEvent) { - IAudioService audioService = IAudioService.Stub.asInterface( - ServiceManager.checkService(Context.AUDIO_SERVICE)); - if (audioService != null) { - try { - audioService.dispatchMediaKeyEvent(keyEvent); - } catch (RemoteException e) { - Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e); - } + if (USE_SESSIONS) { + MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false); } else { - Slog.w(TAG, "Unable to find IAudioService for media key event."); + IAudioService audioService = IAudioService.Stub.asInterface( + ServiceManager.checkService(Context.AUDIO_SERVICE)); + if (audioService != null) { + try { + audioService.dispatchMediaKeyEvent(keyEvent); + } catch (RemoteException e) { + Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e); + } + } else { + Slog.w(TAG, "Unable to find IAudioService for media key event."); + } } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 00d364b..030e3ed 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -365,8 +365,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return mSessionCb.mCb; } - public void sendMediaButton(KeyEvent ke, ResultReceiver cb) { - mSessionCb.sendMediaButton(ke, cb); + public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) { + mSessionCb.sendMediaButton(ke, sequenceId, cb); } public void dump(PrintWriter pw, String prefix) { @@ -649,11 +649,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mCb = cb; } - public void sendMediaButton(KeyEvent keyEvent, ResultReceiver cb) { + public void sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { - mCb.onMediaButton(mediaButtonIntent, cb); + mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } @@ -789,7 +789,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void sendMediaButton(KeyEvent mediaButtonIntent) { - mSessionCb.sendMediaButton(mediaButtonIntent, null); + mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index d9e45f5ba..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 @@ package com.android.server.media; 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.RemoteException; 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; @@ -61,6 +64,8 @@ public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final int WAKELOCK_TIMEOUT = 5000; + private final SessionManagerImpl mSessionManagerImpl; // private final MediaRouteProviderWatcher mRouteProviderWatcher; private final MediaSessionStack mPriorityStack; @@ -73,6 +78,8 @@ public class MediaSessionService extends SystemService implements Monitor { private final Handler mHandler = new Handler(); private final PowerManager.WakeLock mMediaEventWakeLock; + private KeyguardManager mKeyguardManager; + private MediaSessionRecord mPrioritySession; private int mCurrentUserId = -1; @@ -96,6 +103,8 @@ public class MediaSessionService extends SystemService implements Monitor { publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); Watchdog.getInstance().addMonitor(this); updateUser(); + mKeyguardManager = + (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); } /** @@ -599,6 +608,9 @@ public class MediaSessionService extends SystemService implements Monitor { "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 { @@ -676,31 +688,13 @@ public class MediaSessionService extends SystemService implements Monitor { final long token = Binder.clearCallingIdentity(); try { - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - } synchronized (mLock) { - MediaSessionRecord mbSession = mPriorityStack + MediaSessionRecord session = mPriorityStack .getDefaultMediaButtonSession(mCurrentUserId); - if (mbSession != null) { - if (DEBUG) { - Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo()); - } - mbSession.sendMediaButton(keyEvent, - needWakeLock ? mKeyEventDoneReceiver : null); + if (isVoiceKey(keyEvent.getKeyCode())) { + handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); } else { - 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 { @@ -743,15 +737,171 @@ public class MediaSessionService extends SystemService implements Monitor { } } - ResultReceiver mKeyEventDoneReceiver = new ResultReceiver(mHandler) { + 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 { + private final Handler mHandler; + private int mRefCount = 0; + private int mLastTimeoutId = 0; + + public KeyEventWakeLockReceiver(Handler handler) { + super(handler); + mHandler = handler; + } + + public void onTimeout() { + synchronized (mLock) { + if (mRefCount == 0) { + // We've already released it, so just return + return; + } + mLastTimeoutId++; + mRefCount = 0; + releaseWakeLockLocked(); + } + } + + public void aquireWakeLockLocked() { + if (mRefCount == 0) { + mMediaEventWakeLock.acquire(); + } + mRefCount++; + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, WAKELOCK_TIMEOUT); + + } + + @Override + public void run() { + onTimeout(); + } + @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - synchronized (mLock) { - if (mMediaEventWakeLock.isHeld()) { - mMediaEventWakeLock.release(); + if (resultCode < mLastTimeoutId) { + // Ignore results from calls that were before the last + // timeout, just in case. + return; + } else { + synchronized (mLock) { + if (mRefCount > 0) { + mRefCount--; + if (mRefCount == 0) { + releaseWakeLockLocked(); + } + } } } } + + private void releaseWakeLockLocked() { + mMediaEventWakeLock.release(); + mHandler.removeCallbacks(this); + } }; BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { |