MediaSession2: Protect MediaSessionManager APIs with permission

Bug: 73226436
Test: Locally created custom CTS methods and run
Change-Id: Iec5d0900b30f9ec19faa5beb11a83f4e5696b57e
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 3b5b1bf..7348b84 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -608,7 +608,7 @@
      */
     private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
             int resolvedUserId) {
-        if (isCurrentVolumeController(uid, pid)) return;
+        if (isCurrentVolumeController(pid, uid)) return;
         if (getContext()
                 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
                     != PackageManager.PERMISSION_GRANTED
@@ -618,13 +618,13 @@
         }
     }
 
-    private boolean isCurrentVolumeController(int uid, int pid) {
+    private boolean isCurrentVolumeController(int pid, int uid) {
         return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
                 pid, uid) == PackageManager.PERMISSION_GRANTED;
     }
 
     private void enforceSystemUiPermission(String action, int pid, int uid) {
-        if (!isCurrentVolumeController(uid, pid)) {
+        if (!isCurrentVolumeController(pid, uid)) {
             throw new SecurityException("Only system ui may " + action);
         }
     }
@@ -1501,53 +1501,21 @@
          * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
          * permission or an enabled notification listener)
          *
-         * @param uid uid of the controller app
-         * @param packageName package name of the controller app
+         * @param controllerPackageName package name of the controller app
+         * @param controllerPid pid of the controller app
+         * @param controllerUid uid of the controller app
          */
         @Override
-        public boolean isTrusted(int uid, String packageName) throws RemoteException {
+        public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
+                throws RemoteException {
+            final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                int userId = UserHandle.getUserId(uid);
-                // Sanity check whether uid and packageName matches
-                if (uid != mPackageManager.getPackageUid(packageName, 0, userId)) {
-                    throw new IllegalArgumentException("uid=" + uid + " and packageName="
-                            + packageName + " doesn't match");
-                }
-
-                // Check if it's system server or has MEDIA_CONTENT_CONTROL.
-                // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
-                // check here.
-                if (uid == Process.SYSTEM_UID || mPackageManager.checkPermission(
-                        android.Manifest.permission.MEDIA_CONTENT_CONTROL, packageName, uid)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    return true;
-                }
-                if (DEBUG) {
-                    Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted"
-                            + " MEDIA_CONTENT_CONTROL");
-                }
-
-                // TODO(jaewan): Add hasEnabledNotificationListener(String pkgName) for
-                //               optimization (Post-P)
-                final List<ComponentName> enabledNotificationListeners =
-                        mNotificationManager.getEnabledNotificationListeners(userId);
-                if (enabledNotificationListeners != null) {
-                    for (int i = 0; i < enabledNotificationListeners.size(); i++) {
-                        if (TextUtils.equals(packageName,
-                                enabledNotificationListeners.get(i).getPackageName())) {
-                            return true;
-                        }
-                    }
-                }
+                return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
+                        controllerPid, controllerUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
-            if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled notification"
-                        + " listener");
-            }
-            return false;
         }
 
         /**
@@ -1614,60 +1582,85 @@
             destroySession2Internal(token);
         }
 
-        // TODO(jaewan): Protect this API with permission (b/73226436)
+        // TODO(jaewan): Make this API take userId as an argument (b/73597722)
         @Override
         public List<Bundle> getSessionTokens(boolean activeSessionOnly,
-                boolean sessionServiceOnly) throws RemoteException {
+                boolean sessionServiceOnly, String packageName) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
             List<Bundle> tokens = new ArrayList<>();
-            synchronized (mLock) {
-                for (Map.Entry<SessionToken2, MediaController2> record
-                        : mSessionRecords.entrySet()) {
-                    boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
-                    boolean isActive = record.getValue() != null;
-                    if ((activeSessionOnly && !isActive)
-                            || (sessionServiceOnly && !isSessionService) ){
-                        continue;
+            try {
+                verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
+                synchronized (mLock) {
+                    for (Map.Entry<SessionToken2, MediaController2> record
+                            : mSessionRecords.entrySet()) {
+                        boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
+                        boolean isActive = record.getValue() != null;
+                        if ((activeSessionOnly && !isActive)
+                                || (sessionServiceOnly && !isSessionService)) {
+                            continue;
+                        }
+                        tokens.add(record.getKey().toBundle());
                     }
-                    tokens.add(record.getKey().toBundle());
                 }
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
             return tokens;
         }
 
-        // TODO(jaewan): Protect this API with permission (b/73226436)
-        // TODO(jaewan): "userId != calling user" needs extra protection (b/73226436)
         @Override
         public void addSessionTokensListener(ISessionTokensListener listener, int userId,
-                String packageName) {
-            synchronized (mLock) {
-                final SessionTokensListenerRecord record =
-                        new SessionTokensListenerRecord(listener, userId);
-                try {
-                    listener.asBinder().linkToDeath(record, 0);
-                } catch (RemoteException e) {
+                String packageName) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                int resolvedUserId = verifySessionsRequest2(userId, packageName, pid, uid);
+                synchronized (mLock) {
+                    final SessionTokensListenerRecord record =
+                            new SessionTokensListenerRecord(listener, resolvedUserId);
+                    try {
+                        listener.asBinder().linkToDeath(record, 0);
+                    } catch (RemoteException e) {
+                    }
+                    mSessionTokensListeners.add(record);
                 }
-                mSessionTokensListeners.add(record);
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
 
-        // TODO(jaewan): Protect this API with permission (b/73226436)
+        // TODO(jaewan): Make this API take userId as an argument (b/73597722)
         @Override
-        public void removeSessionTokensListener(ISessionTokensListener listener) {
-            synchronized (mLock) {
-                IBinder listenerBinder = listener.asBinder();
-                for (SessionTokensListenerRecord record : mSessionTokensListeners) {
-                    if (listenerBinder.equals(record.mListener.asBinder())) {
-                        try {
-                            listenerBinder.unlinkToDeath(record, 0);
-                        } catch (NoSuchElementException e) {
+        public void removeSessionTokensListener(ISessionTokensListener listener,
+                String packageName) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
+                synchronized (mLock) {
+                    IBinder listenerBinder = listener.asBinder();
+                    for (SessionTokensListenerRecord record : mSessionTokensListeners) {
+                        if (listenerBinder.equals(record.mListener.asBinder())) {
+                            try {
+                                listenerBinder.unlinkToDeath(record, 0);
+                            } catch (NoSuchElementException e) {
+                            }
+                            mSessionTokensListeners.remove(record);
+                            break;
                         }
-                        mSessionTokensListeners.remove(record);
-                        break;
                     }
                 }
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
 
+        // For MediaSession
         private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
                 final int uid) {
             String packageName = null;
@@ -1687,6 +1680,66 @@
             return resolvedUserId;
         }
 
+        // For MediaSession2
+        private int verifySessionsRequest2(int targetUserId, String callerPackageName,
+                int callerPid, int callerUid) throws RemoteException {
+            // Check that they can make calls on behalf of the user and get the final user id.
+            int resolvedUserId = ActivityManager.handleIncomingUser(callerPid, callerUid,
+                    targetUserId, true /* allowAll */, true /* requireFull */, "getSessionTokens",
+                    callerPackageName);
+            // Check if they have the permissions or their component is
+            // enabled for the user they're calling from.
+            if (!hasMediaControlPermission(
+                    resolvedUserId, callerPackageName, callerPid, callerUid)) {
+                throw new SecurityException("Missing permission to control media.");
+            }
+            return resolvedUserId;
+        }
+
+        // For MediaSession2
+        private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
+                int pid, int uid) throws RemoteException {
+            // Allow API calls from the System UI
+            if (isCurrentVolumeController(pid, uid)) {
+                return true;
+            }
+
+            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+            // check here.
+            if (uid == Process.SYSTEM_UID || getContext().checkPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            } else if (DEBUG) {
+                Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
+            }
+
+            // You may not access another user's content as an enabled listener.
+            final int userId = UserHandle.getUserId(uid);
+            if (resolvedUserId != userId) {
+                return false;
+            }
+
+            // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
+            //               String pkgName) to notification team for optimization
+            final List<ComponentName> enabledNotificationListeners =
+                    mNotificationManager.getEnabledNotificationListeners(userId);
+            if (enabledNotificationListeners != null) {
+                for (int i = 0; i < enabledNotificationListeners.size(); i++) {
+                    if (TextUtils.equals(packageName,
+                            enabledNotificationListeners.get(i).getPackageName())) {
+                        return true;
+                    }
+                }
+            }
+            if (DEBUG) {
+                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
+                        + "notification listener");
+            }
+            return false;
+        }
+
         private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
             MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
                     : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();