summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/session/MediaSessionManager.java1
-rw-r--r--services/core/java/com/android/server/media/MediaKeyDispatcher.java39
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java20
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java23
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java14
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java53
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java9
-rw-r--r--services/core/java/com/android/server/media/SessionPolicyProvider.java63
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);
+}