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

Commit 97e2f089 authored by Sungsoo Lim's avatar Sungsoo Lim Committed by Android (Google) Code Review
Browse files

Merge "Allow to register multiple callbacks in MediaSessionManager"

parents 036426c7 14ddf75c
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);
                        }
                    }