diff options
8 files changed, 205 insertions, 17 deletions
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 5ef466dfd0e9..69be8b307950 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -61,6 +61,7 @@ import java.util.concurrent.Executor; * @see MediaSession * @see MediaController */ +// TODO: (jinpark) Add API for getting and setting session policies from MediaSessionService. @SystemService(Context.MEDIA_SESSION_SERVICE) public final class MediaSessionManager { private static final String TAG = "SessionManager"; diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java new file mode 100644 index 000000000000..16b9eb910ec1 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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 com.android.server.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.session.MediaSession; +import android.view.KeyEvent; + +/** + * Provides a way to customize behavior for media key events. + */ +public interface MediaKeyDispatcher { + /** + * Implement this to customize the logic for which MediaSession should consume which key event. + * + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons. + * @param asSystemService {@code true} if the event came from the system service via hardware + * devices. {@code false} if the event came from the app process through key injection. + * @return a {@link MediaSession.Token} instance that should consume the given key event. + */ + @Nullable + MediaSession.Token getSessionForKeyEvent(@NonNull KeyEvent keyEvent, + boolean asSystemService); +} diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index b21d2e789555..820731d20c00 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -50,15 +50,18 @@ public class MediaSession2Record implements MediaSessionRecordImpl { private final MediaSessionService mService; @GuardedBy("mLock") private boolean mIsConnected; + @GuardedBy("mLock") + private int mPolicies; public MediaSession2Record(Session2Token sessionToken, MediaSessionService service, - Looper handlerLooper) { + Looper handlerLooper, int policies) { mSessionToken = sessionToken; mService = service; mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper)); mController = new MediaController2.Builder(service.getContext(), sessionToken) .setControllerCallback(mHandlerExecutor, new Controller2Callback()) .build(); + mPolicies = policies; } @Override @@ -129,6 +132,21 @@ public class MediaSession2Record implements MediaSessionRecordImpl { return false; } + + @Override + public int getSessionPolicies() { + synchronized (mLock) { + return mPolicies; + } + } + + @Override + public void setSessionPolicies(int policies) { + synchronized (mLock) { + mPolicies = policies; + } + } + @Override public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "token=" + mSessionToken); diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 46fb24048a20..05f7e1d7c3a5 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -159,9 +159,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private long mDuration = -1; private String mMetadataDescription; + private int mPolicies; + public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo, - MediaSessionService service, Looper handlerLooper) throws RemoteException { + MediaSessionService service, Looper handlerLooper, int policies) + throws RemoteException { mOwnerPid = ownerPid; mOwnerUid = ownerUid; mUserId = userId; @@ -178,6 +181,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioAttrs = DEFAULT_ATTRIBUTES; + mPolicies = policies; // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -438,6 +442,20 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override + public int getSessionPolicies() { + synchronized (mLock) { + return mPolicies; + } + } + + @Override + public void setSessionPolicies(int policies) { + synchronized (mLock) { + mPolicies = policies; + } + } + + @Override public void dump(PrintWriter pw, String prefix) { pw.println(prefix + mTag + " " + this); @@ -808,6 +826,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) { + return; + } mMediaButtonReceiver = pi; final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 2cde89a7a6f6..6e1088088ced 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -20,6 +20,8 @@ import android.media.AudioManager; import android.os.ResultReceiver; import android.view.KeyEvent; +import com.android.server.media.SessionPolicyProvider.SessionPolicy; + import java.io.PrintWriter; /** @@ -128,6 +130,18 @@ public interface MediaSessionRecordImpl extends AutoCloseable { KeyEvent ke, int sequenceId, ResultReceiver cb); /** + * Get session policies from custom policy provider set when MediaSessionRecord is instantiated. + * If custom policy does not exist, will return null. + */ + @SessionPolicy + int getSessionPolicies(); + + /** + * Overwrite session policies that have been set when MediaSessionRecord is instantiated. + */ + void setSessionPolicies(@SessionPolicy int policies); + + /** * Dumps internal state * * @param pw print writer diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index a6ad57a7ae3a..d0efef041180 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -141,6 +141,9 @@ public class MediaSessionService extends SystemService implements Monitor { final RemoteCallbackList<IRemoteVolumeController> mRemoteVolumeControllers = new RemoteCallbackList<>(); + private SessionPolicyProvider mCustomSessionPolicyProvider; + private MediaKeyDispatcher mCustomMediaKeyDispatcher; + public MediaSessionService(Context context) { super(context); mContext = context; @@ -179,6 +182,9 @@ public class MediaSessionService extends SystemService implements Monitor { mSettingsObserver.observe(); mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_LEANBACK); + + // TODO: (jinpark) check if config value for custom MediaKeyDispatcher and + // SessionPolicyProvider have been overlayed and instantiate using reflection. updateUser(); } @@ -555,7 +561,8 @@ public class MediaSessionService extends SystemService implements Monitor { * 4. It needs to be added to the relevant user record. */ private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, - String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) { + String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo, + int policies) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { @@ -566,7 +573,8 @@ public class MediaSessionService extends SystemService implements Monitor { final MediaSessionRecord session; try { session = new MediaSessionRecord(callerPid, callerUid, userId, - callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper()); + callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper(), + policies); } catch (RemoteException e) { throw new RuntimeException("Media Session owner died prematurely.", e); } @@ -1127,8 +1135,11 @@ public class MediaSessionService extends SystemService implements Monitor { if (cb == null) { throw new IllegalArgumentException("Controller callback cannot be null"); } + int policies = (mCustomSessionPolicyProvider != null) + ? mCustomSessionPolicyProvider.getSessionPoliciesForApplication( + uid, packageName) : 0; return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag, - sessionInfo).getSessionBinder(); + sessionInfo, policies).getSessionBinder(); } finally { Binder.restoreCallingIdentity(token); } @@ -1148,7 +1159,7 @@ public class MediaSessionService extends SystemService implements Monitor { + " but actually=" + sessionToken.getUid()); } MediaSession2Record record = new MediaSession2Record( - sessionToken, MediaSessionService.this, mHandler.getLooper()); + sessionToken, MediaSessionService.this, mHandler.getLooper(), 0); synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); user.mPriorityStack.addSession(record); @@ -1308,12 +1319,11 @@ public class MediaSessionService extends SystemService implements Monitor { * ACTION_MEDIA_BUTTON intent to the rest of the system. * * @param packageName The caller package - * @param asSystemService {@code true} if the event sent to the session as if it was come - * from the system service instead of the app process. This helps sessions to - * distinguish between the key injection by the app and key events from the - * hardware devices. Should be used only when the volume key events aren't handled - * by foreground activity. {@code false} otherwise to tell session about the real - * caller. + * @param asSystemService {@code true} if the event sent to the session came from the + * service instead of the app process. This helps sessions to distinguish between + * the key injection by the app and key events from the hardware devices. Should be + * used only when the hardware key events aren't handled by foreground activity. + * {@code false} otherwise to tell session about the real caller. * @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 @@ -2115,14 +2125,27 @@ public class MediaSessionService extends SystemService implements Monitor { // TODO(jaewan): Implement return; } - MediaSessionRecord session = - (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked(); + MediaSessionRecord session = null; + + // Retrieve custom session for key event if it exists. + if (mCustomMediaKeyDispatcher != null) { + MediaSession.Token token = + mCustomMediaKeyDispatcher.getSessionForKeyEvent(keyEvent, asSystemService); + if (token != null) { + session = getMediaSessionRecordLocked(token); + } + } + + if (session == null) { + session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked(); + } + if (session != null) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to " + session); } if (needWakeLock) { - mKeyEventReceiver.aquireWakeLockLocked(); + mKeyEventReceiver.acquireWakeLockLocked(); } // If we don't need a wakelock use -1 as the id so we won't release it later. session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent, @@ -2140,7 +2163,7 @@ public class MediaSessionService extends SystemService implements Monitor { } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { if (needWakeLock) { - mKeyEventReceiver.aquireWakeLockLocked(); + mKeyEventReceiver.acquireWakeLockLocked(); } Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -2349,7 +2372,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } - public void aquireWakeLockLocked() { + public void acquireWakeLockLocked() { if (mRefCount == 0) { mMediaEventWakeLock.acquire(); } diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 7bb7cf4b74ad..07b1a1acf466 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -274,6 +274,15 @@ class MediaSessionStack { } private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) { + // Check if the policy states that this session should not be updated as a media button + // session. + if (newMediaButtonSession != null) { + int policies = newMediaButtonSession.getSessionPolicies(); + if ((policies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_SESSION) == 1) { + return; + } + } + MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession; mMediaButtonSession = newMediaButtonSession; mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged( diff --git a/services/core/java/com/android/server/media/SessionPolicyProvider.java b/services/core/java/com/android/server/media/SessionPolicyProvider.java new file mode 100644 index 000000000000..6eb79ef8dd71 --- /dev/null +++ b/services/core/java/com/android/server/media/SessionPolicyProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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 com.android.server.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.media.session.MediaSession; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Interface for customizing {@link MediaSessionService} + */ +public interface SessionPolicyProvider { + @IntDef(value = { + SESSION_POLICY_IGNORE_BUTTON_RECEIVER, + SESSION_POLICY_IGNORE_BUTTON_SESSION + }) + @Retention(RetentionPolicy.SOURCE) + @interface SessionPolicy {} + + /** + * Policy to ignore media button receiver, to not revive the media app when its media session is + * released or the app is dead. + * + * @see MediaSession#setMediaButtonReceiver + */ + int SESSION_POLICY_IGNORE_BUTTON_RECEIVER = 1 << 0; + + /** + * Policy to ignore sessions that should not respond to media key events via + * {@link MediaSessionService}. A typical use case is to explicitly + * ignore sessions that should not respond to media key events even if their playback state has + * changed most recently. + */ + int SESSION_POLICY_IGNORE_BUTTON_SESSION = 1 << 1; + + /** + * Use this to statically set policies for sessions when they are created. + * Use android.media.session.MediaSessionManager#setSessionPolicies(MediaSession.Token, int) + * to dynamically change policies at runtime. + * + * @param uid + * @param packageName + * @return list of policies + */ + @SessionPolicy int getSessionPoliciesForApplication(int uid, @NonNull String packageName); +} |