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

Commit 14ddf75c authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Allow to register multiple callbacks in MediaSessionManager

Bug: 126758528
Test: manually
Change-Id: I613593a88c115a5ac694b8c984f3ae36e9cc1c78
parent b5e735b6
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -3693,8 +3693,18 @@ package android.media.audiopolicy {
package android.media.session {
  public final class MediaSessionManager {
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerCallback(@NonNull android.media.session.MediaSessionManager.Callback, @Nullable android.os.Handler);
    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);
    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterCallback(@NonNull android.media.session.MediaSessionManager.Callback);
  }
  public abstract static class MediaSessionManager.Callback {
    ctor public MediaSessionManager.Callback();
    method public abstract void onAddressedPlayerChanged(android.media.session.MediaSession.Token);
    method public abstract void onAddressedPlayerChanged(android.content.ComponentName);
    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token);
    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName);
  }
  public static interface MediaSessionManager.OnMediaKeyListener {
+2 −1
Original line number Diff line number Diff line
@@ -62,7 +62,8 @@ interface ISessionManager {
    // For PhoneWindowManager to precheck media keys
    boolean isGlobalPriorityActive();

    void setCallback(in ICallback callback);
    void registerCallback(in ICallback callback);
    void unregisterCallback(in ICallback callback);
    void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
    void setOnMediaKeyListener(in IOnMediaKeyListener listener);

+103 −42
Original line number Diff line number Diff line
@@ -46,7 +46,9 @@ import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
@@ -72,6 +74,7 @@ public final class MediaSessionManager {
     * @hide
     */
    public static final int RESULT_MEDIA_KEY_HANDLED = 1;
    private final ISessionManager mService;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
@@ -80,13 +83,21 @@ public final class MediaSessionManager {
    @GuardedBy("mLock")
    private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
            mSession2TokensListeners = new ArrayMap<>();
    private final ISessionManager mService;
    @GuardedBy("mLock")
    private final CallbackStub mCbStub = new CallbackStub();
    @GuardedBy("mLock")
    private final Map<Callback, Handler> mCallbacks = new HashMap<>();
    @GuardedBy("mLock")
    private MediaSession.Token mCurMediaButtonSession;
    @GuardedBy("mLock")
    private ComponentName mCurMediaButtonReceiver;

    private Context mContext;

    private CallbackImpl mCallback;
    private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
    private OnMediaKeyListenerImpl mOnMediaKeyListener;
    // TODO: Remove mLegacyCallback once Bluetooth app stop calling setCallback() method.
    @GuardedBy("mLock")
    private Callback mLegacyCallback;

    /**
     * @hide
@@ -752,18 +763,71 @@ public final class MediaSessionManager {
     *            if the callback should be invoked on the calling thread's looper.
     * @hide
     */
    // TODO: Remove this method once Bluetooth app stop calling it.
    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
        synchronized (mLock) {
            try {
            if (mLegacyCallback != null) {
                unregisterCallback(mLegacyCallback);
            }
            mLegacyCallback = callback;
            if (callback != null) {
                registerCallback(callback, handler);
            }
        }
    }

    /**
     * Register a {@link Callback}.
     *
     * @param callback A {@link Callback}.
     * @param handler The handler on which the callback should be invoked, or {@code null}
     *            if the callback should be invoked on the calling thread's looper.
     * @hide
     */
    @SystemApi
    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
        if (callback == null) {
                    mCallback = null;
                    mService.setCallback(null);
                } else {
            throw new NullPointerException("callback shouldn't be null");
        }
        synchronized (mLock) {
            try {
                if (handler == null) {
                    handler = new Handler();
                }
                    mCallback = new CallbackImpl(callback, handler);
                    mService.setCallback(mCallback);
                mCallbacks.put(callback, handler);
                if (mCurMediaButtonSession != null) {
                    handler.post(() -> callback.onAddressedPlayerChanged(mCurMediaButtonSession));
                } else if (mCurMediaButtonReceiver != null) {
                    handler.post(() -> callback.onAddressedPlayerChanged(mCurMediaButtonReceiver));
                }

                if (mCallbacks.size() == 1) {
                    mService.registerCallback(mCbStub);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to set media key callback", e);
            }
        }
    }

    /**
     * Unregister a {@link Callback}.
     *
     * @param callback A {@link Callback}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
    public void unregisterCallback(@NonNull Callback callback) {
        if (callback == null) {
            throw new NullPointerException("callback shouldn't be null");
        }
        synchronized (mLock) {
            try {
                mCallbacks.remove(callback);
                if (mCallbacks.size() == 0) {
                    mService.unregisterCallback(mCbStub);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to set media key callback", e);
@@ -835,6 +899,7 @@ public final class MediaSessionManager {
     * receive media key events.
     * @hide
     */
    @SystemApi
    public static abstract class Callback {
        /**
         * Called when a media key event is dispatched to the media session
@@ -861,7 +926,7 @@ public final class MediaSessionManager {
        /**
         * Called when the addressed player is changed to a media session.
         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
         * {@link #setCallback} if the addressed player exists.
         * {@link #registerCallback} if the addressed player exists.
         *
         * @param sessionToken The media session's token.
         */
@@ -870,7 +935,7 @@ public final class MediaSessionManager {
        /**
         * Called when the addressed player is changed to the media button receiver.
         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
         * {@link #setCallback} if the addressed player exists.
         * {@link #registerCallback} if the addressed player exists.
         *
         * @param mediaButtonReceiver The media button receiver.
         */
@@ -1076,56 +1141,52 @@ public final class MediaSessionManager {
        }
    }

    private static final class CallbackImpl extends ICallback.Stub {
        private final Callback mCallback;
        private final Handler mHandler;

        public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
            mCallback = callback;
            mHandler = handler;
        }
    private final class CallbackStub extends ICallback.Stub {

        @Override
        public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
                MediaSession.Token sessionToken) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onMediaKeyEventDispatched(event, sessionToken);
            synchronized (mLock) {
                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
                    e.getValue().post(
                            () -> e.getKey().onMediaKeyEventDispatched(event, sessionToken));
                }
            }
            });
        }

        @Override
        public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
                ComponentName mediaButtonReceiver) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
            synchronized (mLock) {
                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
                    e.getValue().post(
                            () -> e.getKey().onMediaKeyEventDispatched(event, mediaButtonReceiver));
                }
            }
            });
        }

        @Override
        public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onAddressedPlayerChanged(sessionToken);
            synchronized (mLock) {
                mCurMediaButtonSession = sessionToken;
                mCurMediaButtonReceiver = null;
                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
                    e.getValue().post(() -> e.getKey().onAddressedPlayerChanged(sessionToken));
                }
            }
            });
        }

        @Override
        public void onAddressedPlayerChangedToMediaButtonReceiver(
                ComponentName mediaButtonReceiver) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
            synchronized (mLock) {
                mCurMediaButtonSession = null;
                mCurMediaButtonReceiver = mediaButtonReceiver;
                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
                    e.getValue().post(() -> e.getKey().onAddressedPlayerChanged(
                            mediaButtonReceiver));
                }
            }
            });
        }
    }
}
+112 −60
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import com.android.server.Watchdog.Monitor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
@@ -748,6 +749,8 @@ public class MediaSessionService extends SystemService implements Monitor {

        private final int mFullUserId;
        private final MediaSessionStack mPriorityStack;
        private final HashMap<IBinder, CallbackRecord> mCallbacks = new HashMap<>();

        private PendingIntent mLastMediaButtonReceiver;
        private ComponentName mRestoredMediaButtonReceiver;
        private int mRestoredMediaButtonReceiverComponentType;
@@ -761,7 +764,6 @@ public class MediaSessionService extends SystemService implements Monitor {

        private IOnMediaKeyListener mOnMediaKeyListener;
        private int mOnMediaKeyListenerUid;
        private ICallback mCallback;

        FullUserRecord(int fullUserId) {
            mFullUserId = fullUserId;
@@ -793,6 +795,24 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
        }

        public void registerCallbackLocked(ICallback callback, int uid) {
            IBinder cbBinder = callback.asBinder();
            CallbackRecord cr = new CallbackRecord(callback, uid);
            mCallbacks.put(cbBinder, cr);
            try {
                cbBinder.linkToDeath(cr, 0);
            } catch (RemoteException e) {
                Log.w(TAG, "Failed to register callback", e);
                mCallbacks.remove(cbBinder);
            }
        }

        public void unregisterCallbackLocked(ICallback callback) {
            IBinder cbBinder = callback.asBinder();
            CallbackRecord cr = mCallbacks.remove(cbBinder);
            cbBinder.unlinkToDeath(cr, 0);
        }

        public void dumpLocked(PrintWriter pw, String prefix) {
            pw.print(prefix + "Record for full_user=" + mFullUserId);
            // Dump managed profile user ids associated with this user.
@@ -811,7 +831,10 @@ public class MediaSessionService extends SystemService implements Monitor {
            pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
            pw.println(indent + "Media key listener package: "
                    + getCallingPackageName(mOnMediaKeyListenerUid));
            pw.println(indent + "Callback: " + mCallback);
            pw.println(indent + "Callbacks: registered " + mCallbacks.size() + " callback(s)");
            for (CallbackRecord cr : mCallbacks.values()) {
                pw.println(indent + "  from " + getCallingPackageName(cr.uid));
            }
            pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
            pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
            pw.println(indent + "Restored MediaButtonReceiverComponentType: "
@@ -871,21 +894,18 @@ public class MediaSessionService extends SystemService implements Monitor {
                    mFullUserId);
        }

        private void pushAddressedPlayerChangedLocked() {
            if (mCallback == null) {
                return;
            }
        private void pushAddressedPlayerChangedLocked(ICallback callback) {
            try {
                MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
                if (mediaButtonSession != null) {
                    mCallback.onAddressedPlayerChangedToMediaSession(
                    callback.onAddressedPlayerChangedToMediaSession(
                            mediaButtonSession.getSessionToken());
                } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
                            mCurrentFullUserRecord.mLastMediaButtonReceiver
                                    .getIntent().getComponent());
                } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
                }
            } catch (RemoteException e) {
@@ -893,6 +913,12 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
        }

        private void pushAddressedPlayerChangedLocked() {
            for (CallbackRecord cr : mCallbacks.values()) {
                pushAddressedPlayerChangedLocked(cr.callback);
            }
        }

        private MediaSessionRecord getMediaButtonSessionLocked() {
            return isGlobalPriorityActiveLocked()
                    ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
@@ -926,6 +952,23 @@ public class MediaSessionService extends SystemService implements Monitor {
            // Pick legacy behavior for BroadcastReceiver or unknown.
            return COMPONENT_TYPE_BROADCAST;
        }

        final class CallbackRecord implements IBinder.DeathRecipient {
            public final ICallback callback;
            public final int uid;

            CallbackRecord(ICallback callback, int uid) {
                this.callback = callback;
                this.uid = uid;
            }

            @Override
            public void binderDied() {
                synchronized (mLock) {
                    mCallbacks.remove(callback.asBinder());
                }
            }
        }
    }

    final class SessionsListenerRecord implements IBinder.DeathRecipient {
@@ -1305,44 +1348,53 @@ public class MediaSessionService extends SystemService implements Monitor {
        }

        @Override
        public void setCallback(ICallback callback) {
        public void registerCallback(final ICallback callback) {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final int userId = UserHandle.getUserId(uid);
            final long token = Binder.clearCallingIdentity();
            try {
                if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
                    throw new SecurityException("Only Bluetooth service processes can set"
                            + " Callback");
                if (!hasMediaControlPermission(pid, uid)) {
                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
                            + "  register Callback");
                }
                synchronized (mLock) {
                    int userId = UserHandle.getUserId(uid);
                    FullUserRecord user = getFullUserRecordLocked(userId);
                    if (user == null || user.mFullUserId != userId) {
                        Log.w(TAG, "Only the full user can set the callback"
                        Log.w(TAG, "Only the full user can register the callback"
                                + ", userId=" + userId);
                        return;
                    }
                    user.mCallback = callback;
                    Log.d(TAG, "The callback " + user.mCallback
                            + " is set by " + getCallingPackageName(uid));
                    if (user.mCallback == null) {
                        return;
                    user.registerCallbackLocked(callback, uid);
                    Log.d(TAG, "The callback (" + callback.asBinder()
                            + ") is registered by " + getCallingPackageName(uid));
                }
                    try {
                        user.mCallback.asBinder().linkToDeath(
                                new IBinder.DeathRecipient() {
                                    @Override
                                    public void binderDied() {
                                        synchronized (mLock) {
                                            user.mCallback = null;
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
                                }, 0);
                        user.pushAddressedPlayerChangedLocked();
                    } catch (RemoteException e) {
                        Log.w(TAG, "Failed to set callback", e);
                        user.mCallback = null;

        @Override
        public void unregisterCallback(final ICallback callback) {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final int userId = UserHandle.getUserId(uid);
            final long token = Binder.clearCallingIdentity();
            try {
                if (!hasMediaControlPermission(pid, uid)) {
                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
                            + "  unregister Callback");
                }
                synchronized (mLock) {
                    FullUserRecord user = getFullUserRecordLocked(userId);
                    if (user == null || user.mFullUserId != userId) {
                        Log.w(TAG, "Only the full user can unregister the callback"
                                + ", userId=" + userId);
                        return;
                    }
                    user.unregisterCallbackLocked(callback);
                    Log.d(TAG, "The callback (" + callback.asBinder()
                            + ") is unregistered by " + getCallingPackageName(uid));
                }
            } finally {
                Binder.restoreCallingIdentity(token);
@@ -1771,6 +1823,7 @@ public class MediaSessionService extends SystemService implements Monitor {
        public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
                throws RemoteException {
            final int uid = Binder.getCallingUid();
            final int userId = UserHandle.getUserId(uid);
            final long token = Binder.clearCallingIdentity();
            try {
                // Don't perform sanity check between controllerPackageName and controllerUid.
@@ -1781,8 +1834,8 @@ public class MediaSessionService extends SystemService implements Monitor {
                // Note that we can use Context#getOpPackageName() instead of
                // Context#getPackageName() for getting package name that matches with the PID/UID,
                // but it doesn't tell which package has created the MediaController, so useless.
                return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
                        controllerPid, controllerUid);
                return hasMediaControlPermission(controllerPid, controllerUid)
                        || hasEnabledNotificationListener(userId, controllerPackageName);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
@@ -1808,13 +1861,7 @@ public class MediaSessionService extends SystemService implements Monitor {
            return resolvedUserId;
        }

        private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
                int pid, int uid) throws RemoteException {
            // Allow API calls from the System UI and Settings
            if (hasStatusBarServicePermission(pid, uid)) {
                return true;
            }

        private boolean hasMediaControlPermission(int pid, int uid) {
            // 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.
@@ -1823,11 +1870,15 @@ public class MediaSessionService extends SystemService implements Monitor {
                    == PackageManager.PERMISSION_GRANTED) {
                return true;
            } else if (DEBUG) {
                Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
                Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
            }
            return false;
        }

        private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName)
                throws RemoteException {
            // You may not access another user's content as an enabled listener.
            final int userId = UserHandle.getUserId(uid);
            final int userId = UserHandle.getUserId(resolvedUserId);
            if (resolvedUserId != userId) {
                return false;
            }
@@ -1845,7 +1896,7 @@ public class MediaSessionService extends SystemService implements Monitor {
                }
            }
            if (DEBUG) {
                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
                Log.d(TAG, packageName + " (uid=" + resolvedUserId + ") doesn't have an enabled "
                        + "notification listener");
            }
            return false;
@@ -1950,14 +2001,15 @@ public class MediaSessionService extends SystemService implements Monitor {
                session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                        mKeyEventReceiver);
                if (mCurrentFullUserRecord.mCallback != null) {
                try {
                        mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
                    for (FullUserRecord.CallbackRecord cr
                            : mCurrentFullUserRecord.mCallbacks.values()) {
                        cr.callback.onMediaKeyEventDispatchedToMediaSession(
                                keyEvent, session.getSessionToken());
                    }
                } catch (RemoteException e) {
                    Log.w(TAG, "Failed to send callback", e);
                }
                }
            } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
                    || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
                if (needWakeLock) {
@@ -1980,12 +2032,12 @@ public class MediaSessionService extends SystemService implements Monitor {
                        receiver.send(mContext,
                                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                mediaButtonIntent, mKeyEventReceiver, mHandler);
                        if (mCurrentFullUserRecord.mCallback != null) {
                        ComponentName componentName = mCurrentFullUserRecord
                                .mLastMediaButtonReceiver.getIntent().getComponent();
                        if (componentName != null) {
                                mCurrentFullUserRecord.mCallback
                                        .onMediaKeyEventDispatchedToMediaButtonReceiver(
                            for (FullUserRecord.CallbackRecord cr
                                    : mCurrentFullUserRecord.mCallbacks.values()) {
                                cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
                                                keyEvent, componentName);
                            }
                        }
@@ -2018,9 +2070,9 @@ public class MediaSessionService extends SystemService implements Monitor {
                            Log.w(TAG, "Error sending media button to the restored intent "
                                    + receiver + ", type=" + componentType, e);
                        }
                        if (mCurrentFullUserRecord.mCallback != null) {
                            mCurrentFullUserRecord.mCallback
                                    .onMediaKeyEventDispatchedToMediaButtonReceiver(
                        for (FullUserRecord.CallbackRecord cr
                                : mCurrentFullUserRecord.mCallbacks.values()) {
                            cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
                                            keyEvent, receiver);
                        }
                    }