Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ece8ebbb authored by Hyundo Moon's avatar Hyundo Moon
Browse files

Resolve API review on getting media key session

This CL also fixes a bug introduced by previous CLs which added the
public APIs: An app can bypass the security checks by giving other
app's enabled notification listener.
It is done by adding package name verifications which were missing.

Bug: 195657150
Test: Passed MediaSessionManager(Host)Test
Change-Id: Id8d001744203bbbf18bdcb98da2a135a7e8e0509
parent b7f10f7c
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -25266,12 +25266,12 @@ package android.media.session {
  public final class MediaSessionManager {
    method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName);
    method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler);
    method public void addOnMediaKeyEventSessionChangedListener(@NonNull android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
    method public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
    method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
    method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler);
    method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
    method @Nullable public android.media.session.MediaSession.Token getMediaKeyEventSession(@NonNull android.content.ComponentName);
    method @NonNull public String getMediaKeyEventSessionPackageName(@NonNull android.content.ComponentName);
    method @Nullable public android.media.session.MediaSession.Token getMediaKeyEventSession();
    method @NonNull public String getMediaKeyEventSessionPackageName();
    method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
    method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
    method @Deprecated public void notifySession2Created(@NonNull android.media.Session2Token);
+0 −3
Original line number Diff line number Diff line
@@ -5583,9 +5583,6 @@ package android.media.session {
  public final class MediaSessionManager {
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
    method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.media.session.MediaSession.Token getMediaKeyEventSession();
    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public String getMediaKeyEventSessionPackageName();
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
    method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
    method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
+3 −3
Original line number Diff line number Diff line
@@ -39,8 +39,8 @@ interface ISessionManager {
    ISession createSession(String packageName, in ISessionCallback sessionCb, String tag,
            in Bundle sessionInfo, int userId);
    List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
    MediaSession.Token getMediaKeyEventSession(in ComponentName compName);
    String getMediaKeyEventSessionPackageName(in ComponentName compName);
    MediaSession.Token getMediaKeyEventSession(String packageName);
    String getMediaKeyEventSessionPackageName(String packageName);
    void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
            boolean needWakeLock);
    boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
@@ -67,7 +67,7 @@ interface ISessionManager {
    void removeOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
    void addOnMediaKeyEventSessionChangedListener(
            in IOnMediaKeyEventSessionChangedListener listener,
            in ComponentName notificationListener);
            String packageName);
    void removeOnMediaKeyEventSessionChangedListener(
            in IOnMediaKeyEventSessionChangedListener listener);
    void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
+19 −80
Original line number Diff line number Diff line
@@ -194,82 +194,45 @@ public final class MediaSessionManager {
        return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
    }

    /**
     * Gets the media key event session, which would receive a media key event unless specified.
     * @return The media key event session, which would receive key events by default, unless
     *          the caller has specified the target. Can be {@code null}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
    @Nullable
    public MediaSession.Token getMediaKeyEventSession() {
        try {
            return mService.getMediaKeyEventSession(null);
        } catch (RemoteException ex) {
            Log.e(TAG, "Failed to get media key event session", ex);
        }
        return null;
    }

    /**
     * Gets the media key event session, which would receive a media key event unless specified.
     * <p>
     * This requires that your app is an enabled notificationlistener using the
     * {@link NotificationListenerService} APIs, in which case you must pass
     * the {@link ComponentName} of your enabled listener.
     * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL}
     * permission be held by the calling app, or the app has an enabled notification listener
     * using the {@link NotificationListenerService} APIs. If none of them applies, it will throw
     * a {@link SecurityException}.
     *
     * @return The media key event session, which would receive key events by default, unless
     *          the caller has specified the target. Can be {@code null}.
     */
    @Nullable
    public MediaSession.Token getMediaKeyEventSession(@NonNull ComponentName notificationListener) {
        Objects.requireNonNull(notificationListener, "notificationListener shouldn't be null");
    public MediaSession.Token getMediaKeyEventSession() {
        try {
            return mService.getMediaKeyEventSession(notificationListener);
            return mService.getMediaKeyEventSession(mContext.getPackageName());
        } catch (RemoteException ex) {
            Log.e(TAG, "Failed to get media key event session", ex);
        }
        return null;
    }

    /**
     * Gets the package name of the media key event session.
     * @return The package name of the media key event session or the last session's media button
     *          receiver if the media key event session is {@code null}.
     * @see #getMediaKeyEventSession()
     * @hide
     */
    @SystemApi
    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
    @NonNull
    public String getMediaKeyEventSessionPackageName() {
        try {
            String packageName = mService.getMediaKeyEventSessionPackageName(null);
            return (packageName != null) ? packageName : "";
        } catch (RemoteException ex) {
            Log.e(TAG, "Failed to get media key event session package name", ex);
        }
        return "";
    }

    /**
     * Gets the package name of the media key event session.
     * <p>
     * This requires that your app is an enabled notificationlistener using the
     * {@link NotificationListenerService} APIs, in which case you must pass
     * the {@link ComponentName} of your enabled listener.
     * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL}
     * permission be held by the calling app, or the app has an enabled notification listener
     * using the {@link NotificationListenerService} APIs. If none of them applies, it will throw
     * a {@link SecurityException}.
     *
     * @return The package name of the media key event session or the last session's media button
     *          receiver if the media key event session is {@code null}. Returns an empty string
     *          if neither of them exists.
     * @see #getMediaKeyEventSession(ComponentName)
     * @see #getMediaKeyEventSession()
     */
    @NonNull
    public String getMediaKeyEventSessionPackageName(@NonNull ComponentName notificationListener) {
        Objects.requireNonNull(notificationListener, "notificationListener shouldn't be null");
    public String getMediaKeyEventSessionPackageName() {
        try {
            String packageName = mService.getMediaKeyEventSessionPackageName(notificationListener);
            String packageName = mService.getMediaKeyEventSessionPackageName(
                    mContext.getPackageName());
            return (packageName != null) ? packageName : "";
        } catch (RemoteException ex) {
            Log.e(TAG, "Failed to get media key event session package name", ex);
@@ -938,42 +901,18 @@ public final class MediaSessionManager {
        }
    }

    /**
     * Add a {@link OnMediaKeyEventSessionChangedListener}.
     *
     * @param executor The executor on which the listener should be invoked
     * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void addOnMediaKeyEventSessionChangedListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnMediaKeyEventSessionChangedListener listener) {
        addOnMediaKeyEventSessionChangedListenerInternal(null, executor, listener);
    }

    /**
     * Add a listener to be notified when the media key session is changed.
     * <p>
     * This requires that your app is an enabled notificationlistener using the
     * {@link NotificationListenerService} APIs, in which case you must pass
     * the {@link ComponentName} of your enabled listener.
     * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL}
     * permission be held by the calling app, or the app has an enabled notification listener
     * using the {@link NotificationListenerService} APIs. If none of them applies, it will throw
     * a {@link SecurityException}.
     *
     * @param notificationListener The enabled notification listener component.
     * @param executor The executor on which the listener should be invoked.
     * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
     */
    public void addOnMediaKeyEventSessionChangedListener(
            @NonNull ComponentName notificationListener,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnMediaKeyEventSessionChangedListener listener) {
        Objects.requireNonNull(notificationListener, "notificationListener shouldn't be null");
        addOnMediaKeyEventSessionChangedListenerInternal(notificationListener, executor, listener);
    }

    private void addOnMediaKeyEventSessionChangedListenerInternal(
            @Nullable ComponentName notificationListener,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnMediaKeyEventSessionChangedListener listener) {
        Objects.requireNonNull(executor, "executor shouldn't be null");
@@ -982,7 +921,7 @@ public final class MediaSessionManager {
            try {
                if (mMediaKeyEventSessionChangedCallbacks.isEmpty()) {
                    mService.addOnMediaKeyEventSessionChangedListener(
                            mOnMediaKeyEventSessionChangedListenerStub, notificationListener);
                            mOnMediaKeyEventSessionChangedListenerStub, mContext.getPackageName());
                }
                mMediaKeyEventSessionChangedCallbacks.put(listener, executor);
                executor.execute(
+30 −45
Original line number Diff line number Diff line
@@ -471,7 +471,9 @@ public class MediaSessionService extends SystemService implements Monitor {
            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
                SessionsListenerRecord listener = mSessionsListeners.get(i);
                try {
                    enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
                    String packageName = listener.componentName == null ? null :
                            listener.componentName.getPackageName();
                    enforceMediaPermissions(packageName, listener.pid, listener.uid,
                            listener.userId);
                } catch (SecurityException e) {
                    Log.i(TAG, "ActiveSessionsListener " + listener.componentName
@@ -593,15 +595,13 @@ public class MediaSessionService extends SystemService implements Monitor {
     * for the caller's user</li>
     * </ul>
     */
    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
    private void enforceMediaPermissions(String packageName, int pid, int uid,
            int resolvedUserId) {
        if (hasStatusBarServicePermission(pid, uid)) return;
        // TODO: Refactor to use hasMediaControlPermission and hasEnabledNotificationListener
        if (mContext
                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
                != PackageManager.PERMISSION_GRANTED
                && !isEnabledNotificationListener(compName,
                UserHandle.getUserHandleForUid(uid), resolvedUserId)) {
        if (hasMediaControlPermission(pid, uid)) return;

        if (packageName == null || !hasEnabledNotificationListener(
                packageName, UserHandle.getUserHandleForUid(uid), resolvedUserId)) {
            throw new SecurityException("Missing permission to control media.");
        }
    }
@@ -632,29 +632,26 @@ public class MediaSessionService extends SystemService implements Monitor {
    }

    /**
     * This checks if the component is an enabled notification listener for the
     * This checks if the given package has an enabled notification listener for the
     * specified user. Enabled components may only operate on behalf of the user
     * they're running as.
     *
     * @param compName The component that is enabled.
     * @param packageName The package name.
     * @param userHandle The user handle of the caller.
     * @param forUserId The user id they're making the request on behalf of.
     * @return True if the component is enabled, false otherwise
     * @return True if the app has an enabled notification listener for the user, false otherwise
     */
    private boolean isEnabledNotificationListener(ComponentName compName, UserHandle userHandle,
            int forUserId) {
    private boolean hasEnabledNotificationListener(String packageName,
            UserHandle userHandle, int forUserId) {
        if (userHandle.getIdentifier() != forUserId) {
            // You may not access another user's content as an enabled listener.
            return false;
        }
        if (DEBUG) {
            Log.d(TAG, "Checking if enabled notification listener " + compName);
        }
        if (compName != null) {
            return mNotificationManager.hasEnabledNotificationListener(compName.getPackageName(),
                    userHandle);
            Log.d(TAG, "Checking whether the package " + packageName + " has an"
                    + " enabled notification listener.");
        }
        return false;
        return mNotificationManager.hasEnabledNotificationListener(packageName, userHandle);
    }

    /*
@@ -1237,20 +1234,16 @@ public class MediaSessionService extends SystemService implements Monitor {
        }

        @Override
        public MediaSession.Token getMediaKeyEventSession(ComponentName notificationListener) {
        public MediaSession.Token getMediaKeyEventSession(final String packageName) {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
            final int userId = userHandle.getIdentifier();
            final long token = Binder.clearCallingIdentity();
            try {
                if (!hasMediaControlPermission(pid, uid)
                        && !isEnabledNotificationListener(
                                notificationListener, userHandle, userId)) {
                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission or an enabled"
                            + " notification listener is required to"
                            + " get media key event session.");
                }
                enforcePackageName(packageName, uid);
                enforceMediaPermissions(packageName, pid, uid, userId);

                MediaSessionRecordImpl record;
                synchronized (mLock) {
                    FullUserRecord user = getFullUserRecordLocked(userId);
@@ -1272,20 +1265,16 @@ public class MediaSessionService extends SystemService implements Monitor {
        }

        @Override
        public String getMediaKeyEventSessionPackageName(ComponentName notificationListener) {
        public String getMediaKeyEventSessionPackageName(final String packageName) {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
            final int userId = userHandle.getIdentifier();
            final long token = Binder.clearCallingIdentity();
            try {
                if (!hasMediaControlPermission(pid, uid)
                        && !isEnabledNotificationListener(
                        notificationListener, userHandle, userId)) {
                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission or an enabled"
                            + " notification listener is required to"
                            + " get media key event session package name");
                }
                enforcePackageName(packageName, uid);
                enforceMediaPermissions(packageName, pid, uid, userId);

                MediaSessionRecordImpl record;
                synchronized (mLock) {
                    FullUserRecord user = getFullUserRecordLocked(userId);
@@ -1597,7 +1586,7 @@ public class MediaSessionService extends SystemService implements Monitor {
        @Override
        public void addOnMediaKeyEventSessionChangedListener(
                final IOnMediaKeyEventSessionChangedListener listener,
                final ComponentName notificationListener) {
                final String packageName) {
            if (listener == null) {
                Log.w(TAG, "addOnMediaKeyEventSessionChangedListener: listener is null, ignoring");
                return;
@@ -1609,13 +1598,9 @@ public class MediaSessionService extends SystemService implements Monitor {
            final int userId = userHandle.getIdentifier();
            final long token = Binder.clearCallingIdentity();
            try {
                if (!hasMediaControlPermission(pid, uid)
                        && !isEnabledNotificationListener(
                                notificationListener, userHandle, userId)) {
                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission or an enabled"
                            + " notification listener is required to"
                            + " add MediaKeyEventSessionChangedListener.");
                }
                enforcePackageName(packageName, uid);
                enforceMediaPermissions(packageName, pid, uid, userId);

                synchronized (mLock) {
                    FullUserRecord user = getFullUserRecordLocked(userId);
                    if (user == null || user.mFullUserId != userId) {
@@ -1625,7 +1610,7 @@ public class MediaSessionService extends SystemService implements Monitor {
                    }
                    user.addOnMediaKeyEventSessionChangedListenerLocked(listener, uid);
                    Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
                            + ") is added by " + getCallingPackageName(uid));
                            + ") is added by " + packageName);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
@@ -2128,7 +2113,7 @@ public class MediaSessionService extends SystemService implements Monitor {
            int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
            // Check if they have the permissions or their component is enabled for the user
            // they're calling from.
            enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
            enforceMediaPermissions(packageName, pid, uid, resolvedUserId);
            return resolvedUserId;
        }