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

Commit caf49658 authored by Hyundo Moon's avatar Hyundo Moon Committed by android-build-merger
Browse files

Merge "MediaSession2: Introduce connection hints" into qt-dev

am: cf44c411

Change-Id: Ie086575a732b80065dbbc510a1593e9d50f45922
parents 2b9d7322 cf44c411
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -24646,8 +24646,6 @@ package android.media {
  }
  public class MediaController2 implements java.lang.AutoCloseable {
    ctor public MediaController2(@NonNull android.content.Context, @NonNull android.media.Session2Token);
    ctor public MediaController2(@NonNull android.content.Context, @NonNull android.media.Session2Token, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback);
    method public void cancelSessionCommand(@NonNull Object);
    method public void close();
    method @Nullable public android.media.Session2Token getConnectedSessionToken();
@@ -24655,6 +24653,13 @@ package android.media {
    method @NonNull public Object sendSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle);
  }
  public static final class MediaController2.Builder {
    ctor public MediaController2.Builder(@NonNull android.content.Context, @NonNull android.media.Session2Token);
    method @NonNull public android.media.MediaController2 build();
    method @NonNull public android.media.MediaController2.Builder setConnectionHints(@NonNull android.os.Bundle);
    method @NonNull public android.media.MediaController2.Builder setControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback);
  }
  public abstract static class MediaController2.ControllerCallback {
    ctor public MediaController2.ControllerCallback();
    method public void onCommandResult(@NonNull android.media.MediaController2, @NonNull Object, @NonNull android.media.Session2Command, @NonNull android.media.Session2Command.Result);
@@ -25844,6 +25849,7 @@ package android.media {
  }
  public static final class MediaSession2.ControllerInfo {
    method @NonNull public android.os.Bundle getConnectionHints();
    method @NonNull public String getPackageName();
    method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getRemoteUserInfo();
    method public int getUid();
@@ -25863,7 +25869,7 @@ package android.media {
    method public final void addSession(@NonNull android.media.MediaSession2);
    method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
    method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
    method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
    method @Nullable public abstract android.media.MediaSession2 onGetSession(@NonNull android.media.MediaSession2.ControllerInfo);
    method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
    method public final void removeSession(@NonNull android.media.MediaSession2);
    field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ class MediaConstants {
    static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
    static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE";
    static final String KEY_TOKEN_EXTRAS = "android.media.key.TOKEN_EXTRAS";
    static final String KEY_CONNECTION_HINTS = "android.media.key.CONNECTION_HINTS";

    private MediaConstants() {
    }
+105 −21
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.media;

import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
import static android.media.MediaConstants.KEY_PACKAGE_NAME;
import static android.media.MediaConstants.KEY_PID;
import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
@@ -91,24 +92,16 @@ public class MediaController2 implements AutoCloseable {
     * Create a {@link MediaController2} from the {@link Session2Token}.
     * This connects to the session and may wake up the service if it's not available.
     *
     * @param context Context
     * @param token token to connect to
     */
    public MediaController2(@NonNull Context context, @NonNull Session2Token token) {
        this(context, token, context.getMainExecutor(), new ControllerCallback() {});
    }

    /**
     * Create a {@link MediaController2} from the {@link Session2Token}.
     * This connects to the session and may wake up the service if it's not available.
     *
     * @param context Context
     * @param context context
     * @param token token to connect to
     * @param connectionHints a session-specific argument to send to the session when connecting.
     *                        The contents of this bundle may affect the connection result.
     * @param executor executor to run callbacks on.
     * @param callback controller callback to receive changes in.
     */
    public MediaController2(@NonNull Context context, @NonNull Session2Token token,
            @NonNull Executor executor, @NonNull ControllerCallback callback) {
    MediaController2(@NonNull Context context, @NonNull Session2Token token,
            @Nullable Bundle connectionHints, @NonNull Executor executor,
            @NonNull ControllerCallback callback) {
        if (context == null) {
            throw new IllegalArgumentException("context shouldn't be null");
        }
@@ -130,9 +123,9 @@ public class MediaController2 implements AutoCloseable {
        boolean connectRequested;
        if (token.getType() == TYPE_SESSION) {
            mServiceConnection = null;
            connectRequested = requestConnectToSession();
            connectRequested = requestConnectToSession(connectionHints);
        } else {
            mServiceConnection = new SessionServiceConnection();
            mServiceConnection = new SessionServiceConnection(connectionHints);
            connectRequested = requestConnectToService();
        }
        if (!connectRequested) {
@@ -350,16 +343,17 @@ public class MediaController2 implements AutoCloseable {
        }
    }

    private Bundle createConnectionRequest() {
    private Bundle createConnectionRequest(@Nullable Bundle connectionHints) {
        Bundle connectionRequest = new Bundle();
        connectionRequest.putString(KEY_PACKAGE_NAME, mContext.getPackageName());
        connectionRequest.putInt(KEY_PID, Process.myPid());
        connectionRequest.putBundle(KEY_CONNECTION_HINTS, connectionHints);
        return connectionRequest;
    }

    private boolean requestConnectToSession() {
    private boolean requestConnectToSession(@Nullable Bundle connectionHints) {
        Session2Link sessionBinder = mSessionToken.getSessionLink();
        Bundle connectionRequest = createConnectionRequest();
        Bundle connectionRequest = createConnectionRequest(connectionHints);
        try {
            sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
        } catch (RuntimeException e) {
@@ -401,6 +395,93 @@ public class MediaController2 implements AutoCloseable {
        return true;
    }

    /**
     * Builder for {@link MediaController2}.
     * <p>
     * Any incoming event from the {@link MediaSession2} will be handled on the callback
     * executor. If it's not set, {@link Context#getMainExecutor()} will be used by default.
     */
    public static final class Builder {
        private Context mContext;
        private Session2Token mToken;
        private Bundle mConnectionHints;
        private Executor mCallbackExecutor;
        private ControllerCallback mCallback;

        /**
         * Creates a builder for {@link MediaController2}.
         *
         * @param context context
         * @param token token of the session to connect to
         */
        public Builder(@NonNull Context context, @NonNull Session2Token token) {
            if (context == null) {
                throw new IllegalArgumentException("context shouldn't be null");
            }
            if (token == null) {
                throw new IllegalArgumentException("token shouldn't be null");
            }
            mContext = context;
            mToken = token;
        }

        /**
         * Set the connection hints for the controller.
         * <p>
         * {@code connectionHints} is a session-specific argument to send to the session when
         * connecting. The contents of this bundle may affect the connection result.
         *
         * @param connectionHints a bundle which contains the connection hints
         * @return The Builder to allow chaining
         */
        @NonNull
        public Builder setConnectionHints(@NonNull Bundle connectionHints) {
            if (connectionHints == null) {
                throw new IllegalArgumentException("connectionHints shouldn't be null");
            }
            mConnectionHints = new Bundle(connectionHints);
            return this;
        }

        /**
         * Set callback for the controller and its executor.
         *
         * @param executor callback executor
         * @param callback session callback.
         * @return The Builder to allow chaining
         */
        @NonNull
        public Builder setControllerCallback(@NonNull Executor executor,
                @NonNull ControllerCallback callback) {
            if (executor == null) {
                throw new IllegalArgumentException("executor shouldn't be null");
            }
            if (callback == null) {
                throw new IllegalArgumentException("callback shouldn't be null");
            }
            mCallbackExecutor = executor;
            mCallback = callback;
            return this;
        }

        /**
         * Build {@link MediaController2}.
         *
         * @return a new controller
         */
        @NonNull
        public MediaController2 build() {
            if (mCallbackExecutor == null) {
                mCallbackExecutor = mContext.getMainExecutor();
            }
            if (mCallback == null) {
                mCallback = new ControllerCallback() {};
            }
            return new MediaController2(
                    mContext, mToken, mConnectionHints, mCallbackExecutor, mCallback);
        }
    }

    /**
     * Interface for listening to change in activeness of the {@link MediaSession2}.
     * <p>
@@ -469,7 +550,10 @@ public class MediaController2 implements AutoCloseable {

    // This will be called on the main thread.
    private class SessionServiceConnection implements ServiceConnection {
        SessionServiceConnection() {
        private final Bundle mConnectionHints;

        SessionServiceConnection(@Nullable Bundle connectionHints) {
            mConnectionHints = connectionHints;
        }

        @Override
@@ -491,7 +575,7 @@ public class MediaController2 implements AutoCloseable {
                    Log.wtf(TAG, "Service interface is missing.");
                    return;
                }
                Bundle connectionRequest = createConnectionRequest();
                Bundle connectionRequest = createConnectionRequest(mConnectionHints);
                iService.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
                connectRequested = true;
            } catch (RemoteException e) {
+20 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.media;

import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
import static android.media.MediaConstants.KEY_PACKAGE_NAME;
import static android.media.MediaConstants.KEY_PID;
import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
@@ -308,8 +309,11 @@ public class MediaSession2 implements AutoCloseable {
        String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME);

        RemoteUserInfo remoteUserInfo = new RemoteUserInfo(callingPkg, callingPid, callingUid);
        final ControllerInfo controllerInfo = new ControllerInfo(remoteUserInfo,
                mSessionManager.isTrustedForMediaControl(remoteUserInfo), controller);
        final ControllerInfo controllerInfo = new ControllerInfo(
                remoteUserInfo,
                mSessionManager.isTrustedForMediaControl(remoteUserInfo),
                controller,
                connectionRequest.getBundle(KEY_CONNECTION_HINTS));
        mCallbackExecutor.execute(() -> {
            boolean connected = false;
            try {
@@ -568,6 +572,7 @@ public class MediaSession2 implements AutoCloseable {
        private final RemoteUserInfo mRemoteUserInfo;
        private final boolean mIsTrusted;
        private final Controller2Link mControllerBinder;
        private final Bundle mConnectionHints;
        private final Object mLock = new Object();
        //@GuardedBy("mLock")
        private int mNextSeqNumber;
@@ -583,12 +588,16 @@ public class MediaSession2 implements AutoCloseable {
         * @param remoteUserInfo remote user info
         * @param trusted {@code true} if trusted, {@code false} otherwise
         * @param controllerBinder Controller2Link for the connected controller.
         * @param connectionHints a session-specific argument sent from the controller for the
         *                        connection. The contents of this bundle may affect the
         *                        connection result.
         */
        ControllerInfo(@NonNull RemoteUserInfo remoteUserInfo, boolean trusted,
                @Nullable Controller2Link controllerBinder) {
                @Nullable Controller2Link controllerBinder, @Nullable Bundle connectionHints) {
            mRemoteUserInfo = remoteUserInfo;
            mIsTrusted = trusted;
            mControllerBinder = controllerBinder;
            mConnectionHints = connectionHints;
            mPendingCommands = new ArrayMap<>();
            mRequestedCommandSeqNumbers = new ArraySet<>();
        }
@@ -616,6 +625,14 @@ public class MediaSession2 implements AutoCloseable {
            return mRemoteUserInfo.getUid();
        }

        /**
         * @return connection hints sent from controller, or {@link Bundle#EMPTY} if none.
         */
        @NonNull
        public Bundle getConnectionHints() {
            return mConnectionHints == null ? Bundle.EMPTY : new Bundle(mConnectionHints);
        }

        /**
         * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
         * has a enabled notification listener so can be trusted to accept connection and incoming
+55 −22
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package android.media;

import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
import static android.media.MediaConstants.KEY_PACKAGE_NAME;
import static android.media.MediaConstants.KEY_PID;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,6 +28,9 @@ import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaSession2.ControllerInfo;
import android.media.session.MediaSessionManager;
import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -70,6 +77,8 @@ public abstract class MediaSession2Service extends Service {
    //@GuardedBy("mLock")
    private NotificationManager mNotificationManager;
    //@GuardedBy("mLock")
    private MediaSessionManager mMediaSessionManager;
    //@GuardedBy("mLock")
    private Intent mStartSelfIntent;
    //@GuardedBy("mLock")
    private Map<String, MediaSession2> mSessions = new ArrayMap<>();
@@ -93,6 +102,8 @@ public abstract class MediaSession2Service extends Service {
            mStartSelfIntent = new Intent(this, this.getClass());
            mNotificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            mMediaSessionManager =
                    (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
    }

@@ -132,34 +143,22 @@ public abstract class MediaSession2Service extends Service {

    /**
     * Called when a {@link MediaController2} is created with the this service's
     * {@link Session2Token}. Return the primary session for telling the controller which session to
     * connect.
     * <p>
     * Primary session is the highest priority session that this service manages. Here are some
     * recommendations of the primary session.
     * <ol>
     * <li>When there's no {@link MediaSession2}, create and return a new session. Resume the
     * playback that the app has the lastly played with the new session. The behavior is what
     * framework expects when the framework sends key events to the service.</li>
     * <li>When there's multiple {@link MediaSession2}s, pick the session that has the lastly
     * started the playback. This is the same way as the framework prioritize sessions to receive
     * media key events.</li>
     * </ol>
     * {@link Session2Token}. Return the session for telling the controller which session to
     * connect. Return {@code null} to reject the connection from this controller.
     * <p>
     * Session returned here will be added to this service automatically. You don't need to call
     * {@link #addSession(MediaSession2)} for that.
     * <p>
     * Session service will accept or reject the connection with the
     * {@link MediaSession2.SessionCallback} in the session returned here.
     * <p>
     * This method is always called on the main thread.
     *
     * @return a new session
     * @param controllerInfo information of the controller which is trying to connect.
     * @return a {@link MediaSession2} instance for the controller to connect to, or {@code null}
     *         to reject connection
     * @see MediaSession2.Builder
     * @see #getSessions()
     */
    @NonNull
    public abstract MediaSession2 onGetPrimarySession();
    @Nullable
    public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo);

    /**
     * Called when notification UI needs update. Override this method to show or cancel your own
@@ -250,6 +249,16 @@ public abstract class MediaSession2Service extends Service {
        return list;
    }

    /**
     * Returns the {@link MediaSessionManager}.
     */
    @NonNull
    MediaSessionManager getMediaSessionManager() {
        synchronized (mLock) {
            return mMediaSessionManager;
        }
    }

    /**
     * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
     *
@@ -365,8 +374,33 @@ public abstract class MediaSession2Service extends Service {
                            Log.d(TAG, "Handling incoming connection request from the"
                                    + " controller, controller=" + caller + ", uid=" + uid);
                        }

                        String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME);
                        // The Binder.getCallingPid() can be 0 for an oneway call from the
                        // remote process. If it's the case, use PID from the connectionRequest.
                        RemoteUserInfo remoteUserInfo = new RemoteUserInfo(
                                callingPkg,
                                pid == 0 ? connectionRequest.getInt(KEY_PID) : pid,
                                uid);
                        final ControllerInfo controllerInfo = new ControllerInfo(
                                remoteUserInfo,
                                service.getMediaSessionManager()
                                        .isTrustedForMediaControl(remoteUserInfo),
                                caller,
                                connectionRequest.getBundle(KEY_CONNECTION_HINTS));

                        final MediaSession2 session;
                        session = service.onGetPrimarySession();
                        session = service.onGetSession(controllerInfo);

                        if (session == null) {
                            if (DEBUG) {
                                Log.d(TAG, "Rejecting incoming connection request from the"
                                        + " controller, controller=" + caller + ", uid=" + uid);
                            }
                            // Note: Trusted controllers also can be rejected according to the
                            // service implementation.
                            return;
                        }
                        service.addSession(session);
                        shouldNotifyDisconnected = false;
                        session.onConnect(caller, pid, uid, seq, connectionRequest);
@@ -377,8 +411,7 @@ public abstract class MediaSession2Service extends Service {
                        // Trick to call onDisconnected() in one place.
                        if (shouldNotifyDisconnected) {
                            if (DEBUG) {
                                Log.d(TAG, "Service has destroyed prematurely."
                                        + " Rejecting connection");
                                Log.d(TAG, "Notifying the controller of its disconnection");
                            }
                            try {
                                caller.notifyDisconnected(0);
Loading