diff options
3 files changed, 143 insertions, 85 deletions
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 963457bedb60..56664a9839c8 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -52,12 +52,13 @@ interface ISessionManager { void setOnMediaKeyListener(in IOnMediaKeyListener listener); // MediaSession2 - boolean isTrusted(int uid, String packageName); + boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid); boolean createSession2(in Bundle sessionToken); void destroySession2(in Bundle sessionToken); - List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly); + List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly, + String packageName); void addSessionTokensListener(in ISessionTokensListener listener, int userId, String packageName); - void removeSessionTokensListener(in ISessionTokensListener listener); + void removeSessionTokensListener(in ISessionTokensListener listener, String packageName); } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 051321c84d9a..5c79399edc6d 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -342,16 +342,17 @@ public final class MediaSessionManager { /** * Returns whether the api * - * @param uid uid of the app * @param packageName packageName + * @param pid pid of the app + * @param uid uid of the app * @hide */ - public boolean isTrusted(int uid, @NonNull String packageName) { + public boolean isTrusted(@NonNull String packageName, int pid, int uid) { if (packageName == null) { return false; } try { - return mService.isTrusted(uid, packageName); + return mService.isTrusted(packageName, pid, uid); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); } @@ -403,7 +404,8 @@ public final class MediaSessionManager { public List<SessionToken2> getActiveSessionTokens() { try { List<Bundle> bundles = mService.getSessionTokens( - /* activeSessionOnly */ true, /* sessionServiceOnly */ false); + /* activeSessionOnly */ true, /* sessionServiceOnly */ false, + mContext.getPackageName()); return toTokenList(mContext, bundles); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); @@ -424,7 +426,8 @@ public final class MediaSessionManager { public List<SessionToken2> getSessionServiceTokens() { try { List<Bundle> bundles = mService.getSessionTokens( - /* activeSessionOnly */ false, /* sessionServiceOnly */ true); + /* activeSessionOnly */ false, /* sessionServiceOnly */ true, + mContext.getPackageName()); return toTokenList(mContext, bundles); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); @@ -447,7 +450,8 @@ public final class MediaSessionManager { public List<SessionToken2> getAllSessionTokens() { try { List<Bundle> bundles = mService.getSessionTokens( - /* activeSessionOnly */ false, /* sessionServiceOnly */ false); + /* activeSessionOnly */ false, /* sessionServiceOnly */ false, + mContext.getPackageName()); return toTokenList(mContext, bundles); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); @@ -521,7 +525,7 @@ public final class MediaSessionManager { SessionTokensChangedWrapper wrapper = mSessionTokensListener.remove(listener); if (wrapper != null) { try { - mService.removeSessionTokensListener(wrapper.mStub); + mService.removeSessionTokensListener(wrapper.mStub, mContext.getPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error in removeSessionTokensListener.", e); } finally { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 3b5b1bf5e1e2..7348b849a041 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 @@ public class MediaSessionService extends SystemService implements Monitor { */ 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 @@ public class MediaSessionService extends SystemService implements Monitor { } } - 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 @@ public class MediaSessionService extends SystemService implements Monitor { * 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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(); |