diff options
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | media/java/android/media/session/ICallback.aidl | 34 | ||||
| -rw-r--r-- | media/java/android/media/session/ISessionManager.aidl | 5 | ||||
| -rw-r--r-- | media/java/android/media/session/MediaSessionManager.java | 135 | ||||
| -rw-r--r-- | services/core/java/com/android/server/media/MediaSessionService.java | 151 |
5 files changed, 305 insertions, 21 deletions
diff --git a/Android.mk b/Android.mk index 1aaa09a40ffd..77ab10bfe94d 100644 --- a/Android.mk +++ b/Android.mk @@ -404,6 +404,7 @@ LOCAL_SRC_FILES += \ media/java/android/media/projection/IMediaProjectionManager.aidl \ media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \ media/java/android/media/session/IActiveSessionsListener.aidl \ + media/java/android/media/session/ICallback.aidl \ media/java/android/media/session/ISessionController.aidl \ media/java/android/media/session/ISessionControllerCallback.aidl \ media/java/android/media/session/ISession.aidl \ diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl new file mode 100644 index 000000000000..bb8a3cc9b139 --- /dev/null +++ b/media/java/android/media/session/ICallback.aidl @@ -0,0 +1,34 @@ +/* Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.session; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.media.session.MediaSession; +import android.view.KeyEvent; + +/** + * @hide + */ +oneway interface ICallback { + void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event, + in MediaSession.Token sessionToken); + void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event, + in ComponentName mediaButtonReceiver); + + void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken); + void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver); +} diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index bb59e5b4be35..575e7d784a3f 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -18,6 +18,7 @@ package android.media.session; import android.content.ComponentName; import android.media.IRemoteVolumeController; import android.media.session.IActiveSessionsListener; +import android.media.session.ICallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.os.Bundle; @@ -41,4 +42,6 @@ interface ISessionManager { // For PhoneWindowManager to precheck media keys boolean isGlobalPriorityActive(); -}
\ No newline at end of file + + void setCallback(in ICallback callback); +} diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 2364a13e6385..31e60da7fb7a 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -57,6 +57,8 @@ public final class MediaSessionManager { private Context mContext; + private CallbackImpl mCallback; + /** * @hide */ @@ -313,6 +315,36 @@ public final class MediaSessionManager { } /** + * Set a {@link Callback}. + * + * <p>System can only have a single callback, and the callback can only be set by + * Bluetooth service process. + * + * @param callback A {@link Callback}. {@code null} to reset. + * @param handler The handler on which the callback should be invoked, or {@code null} + * if the callback should be invoked on the calling thread's looper. + * @hide + */ + public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { + synchronized (mLock) { + try { + if (callback == null) { + mCallback = null; + mService.setCallback(null); + } else { + if (handler == null) { + handler = new Handler(); + } + mCallback = new CallbackImpl(callback, handler); + mService.setCallback(mCallback); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to set media key callback", e); + } + } + } + + /** * Listens for changes to the list of active sessions. This can be added * using {@link #addOnActiveSessionsChangedListener}. */ @@ -320,6 +352,56 @@ public final class MediaSessionManager { public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); } + /** + * Callbacks for the media session service. + * + * <p>Called when a media key event is dispatched or the addressed player is changed. + * The addressed player is either the media session or the media button receiver that will + * receive media key events. + * @hide + */ + public static abstract class Callback { + /** + * Called when a media key event is dispatched to the media session + * through the media session service. + * + * @param event Dispatched media key event. + * @param sessionToken The media session's token. + */ + public abstract void onMediaKeyEventDispatched(KeyEvent event, + MediaSession.Token sessionToken); + + /** + * Called when a media key event is dispatched to the media button receiver + * through the media session service. + * <p>MediaSessionService may broadcast key events to the media button receiver + * when reviving playback after the media session is released. + * + * @param event Dispatched media key event. + * @param mediaButtonReceiver The media button receiver. + */ + public abstract void onMediaKeyEventDispatched(KeyEvent event, + ComponentName mediaButtonReceiver); + + /** + * Called when the addressed player is changed to a media session. + * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after + * {@link #setCallback} if the addressed player exists. + * + * @param sessionToken The media session's token. + */ + public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken); + + /** + * Called when the addressed player is changed to the media button receiver. + * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after + * {@link #setCallback} if the addressed player exists. + * + * @param mediaButtonReceiver The media button receiver. + */ + public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver); + } + private static final class SessionsChangedWrapper { private Context mContext; private OnActiveSessionsChangedListener mListener; @@ -360,4 +442,57 @@ public final class MediaSessionManager { mHandler = null; } } + + private static final class CallbackImpl extends ICallback.Stub { + private final Callback mCallback; + private final Handler mHandler; + + public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) { + mCallback = callback; + mHandler = handler; + } + + @Override + public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event, + MediaSession.Token sessionToken) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onMediaKeyEventDispatched(event, sessionToken); + } + }); + } + + @Override + public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event, + ComponentName mediaButtonReceiver) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver); + } + }); + } + + @Override + public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onAddressedPlayerChanged(sessionToken); + } + }); + } + + @Override + public void onAddressedPlayerChangedToMediaButtonReceiver( + ComponentName mediaButtonReceiver) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onAddressedPlayerChanged(mediaButtonReceiver); + } + }); + } + } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 4c58ffd5dc83..cc41060a55eb 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -36,6 +36,7 @@ import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; import android.media.session.IActiveSessionsListener; +import android.media.session.ICallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionManager; @@ -101,6 +102,7 @@ public class MediaSessionService extends SystemService implements Monitor { private AudioManagerInternal mAudioManagerInternal; private ContentResolver mContentResolver; private SettingsObserver mSettingsObserver; + private ICallback mCallback; // List of user IDs running in the foreground. // Multiple users can be in the foreground if the work profile is on. @@ -485,6 +487,7 @@ public class MediaSessionService extends SystemService implements Monitor { if (size > 0 && records.get(0).isPlaybackActive(false)) { rememberMediaButtonReceiverLocked(records.get(0)); } + pushAddressedPlayerChangedLocked(); ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>(); for (int i = 0; i < size; i++) { tokens.add(new MediaSession.Token(records.get(i).getControllerBinder())); @@ -516,6 +519,52 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private MediaSessionRecord getMediaButtonSessionLocked() { + // If we don't have a media button receiver to fall back on + // include non-playing sessions for dispatching. + boolean useNotPlayingSessions = true; + for (int userId : mCurrentUserIdList) { + UserRecord ur = mUserRecords.get(userId); + if (ur.mLastMediaButtonReceiver != null + || ur.mRestoredMediaButtonReceiver != null) { + useNotPlayingSessions = false; + break; + } + } + return mPriorityStack.getDefaultMediaButtonSession( + mCurrentUserIdList, useNotPlayingSessions); + } + + private void pushAddressedPlayerChangedLocked() { + if (mCallback == null) { + return; + } + try { + MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked(); + if (mediaButtonSession != null) { + mCallback.onAddressedPlayerChangedToMediaSession( + new MediaSession.Token(mediaButtonSession.getControllerBinder())); + } else { + for (int userId : mCurrentUserIdList) { + UserRecord user = mUserRecords.get(userId); + if (user.mLastMediaButtonReceiver == null + && user.mRestoredMediaButtonReceiver == null) { + continue; + } + ComponentName componentName = user.mLastMediaButtonReceiver != null + ? user.mLastMediaButtonReceiver.getIntent().getComponent() + : user.mRestoredMediaButtonReceiver; + mCallback.onAddressedPlayerChangedToMediaButtonReceiver(componentName); + return; + } + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e); + } + } + + // Remember media button receiver and keep it in the persistent storage. + // This should be called whenever there's no media session to receive media button event. private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) { PendingIntent receiver = record.getMediaButtonReceiver(); UserRecord user = mUserRecords.get(record.getUserId()); @@ -530,6 +579,14 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private String getCallingPackageName(int uid) { + String[] packages = getContext().getPackageManager().getPackagesForUid(uid); + if (packages != null && packages.length > 0) { + return packages[0]; + } + return ""; + } + /** * Information about a particular user. The contents of this object is * guarded by mLock. @@ -792,12 +849,10 @@ public class MediaSessionService extends SystemService implements Monitor { Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions=" + useNotPlayingSessions); } - MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession( - mCurrentUserIdList, useNotPlayingSessions); if (isVoiceKey(keyEvent.getKeyCode())) { - handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); + handleVoiceKeyEventLocked(keyEvent, needWakeLock); } else { - dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); + dispatchMediaKeyEventLocked(keyEvent, needWakeLock); } } } finally { @@ -806,6 +861,45 @@ public class MediaSessionService extends SystemService implements Monitor { } @Override + public void setCallback(ICallback callback) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + if (uid != Process.BLUETOOTH_UID) { + throw new SecurityException("Only Bluetooth service processes can set" + + " Callback"); + } + synchronized (mLock) { + Log.d(TAG, "Callback + " + mCallback + + " is set by " + getCallingPackageName(uid)); + mCallback = callback; + if (mCallback == null) { + return; + } + try { + mCallback.asBinder().linkToDeath( + new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + mCallback = null; + } + } + }, 0); + pushAddressedPlayerChangedLocked(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set callback", e); + mCallback = null; + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + + @Override public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) { final long token = Binder.clearCallingIdentity(); try { @@ -932,13 +1026,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } - 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; - } + private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) { int action = keyEvent.getAction(); boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; if (action == KeyEvent.ACTION_DOWN) { @@ -955,15 +1043,15 @@ public class MediaSessionService extends SystemService implements Monitor { 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); + dispatchMediaKeyEventLocked(downEvent, needWakeLock); + dispatchMediaKeyEventLocked(keyEvent, needWakeLock); } } } } - private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, - MediaSessionRecord session) { + private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) { + MediaSessionRecord session = getMediaButtonSessionLocked(); if (session != null) { if (DEBUG_MEDIA_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to " + session); @@ -977,6 +1065,14 @@ public class MediaSessionService extends SystemService implements Monitor { needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, Process.SYSTEM_UID, getContext().getPackageName()); + if (mCallback != null) { + try { + mCallback.onMediaKeyEventDispatchedToMediaSession(keyEvent, + new MediaSession.Token(session.getControllerBinder())); + } catch (RemoteException e) { + Log.w(TAG, "Failed to send callback", e); + } + } } else { // Launch the last PendingIntent we had with priority for (int userId : mCurrentUserIdList) { @@ -993,26 +1089,41 @@ public class MediaSessionService extends SystemService implements Monitor { mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { if (user.mLastMediaButtonReceiver != null) { + PendingIntent receiver = user.mLastMediaButtonReceiver; if (DEBUG_MEDIA_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent - + " to the last known pendingIntent " - + user.mLastMediaButtonReceiver); + + " to the last known pendingIntent " + receiver); } - user.mLastMediaButtonReceiver.send(getContext(), + receiver.send(getContext(), needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mediaButtonIntent, mKeyEventReceiver, mHandler); + if (mCallback != null) { + ComponentName componentName = + user.mLastMediaButtonReceiver.getIntent().getComponent(); + if (componentName != null) { + mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver( + keyEvent, componentName); + } + } } else { + ComponentName receiver = user.mRestoredMediaButtonReceiver; if (DEBUG_MEDIA_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + user.mRestoredMediaButtonReceiver); + + receiver); } - mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver); + mediaButtonIntent.setComponent(receiver); getContext().sendBroadcastAsUser(mediaButtonIntent, UserHandle.of(userId)); + if (mCallback != null) { + mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver( + keyEvent, receiver); + } } } catch (CanceledException e) { Log.i(TAG, "Error sending key event to media button receiver " + user.mLastMediaButtonReceiver, e); + } catch (RemoteException e) { + Log.w(TAG, "Failed to send callback", e); } return; } |