Loading api/current.txt +9 −3 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); Loading Loading @@ -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(); Loading @@ -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"; media/apex/java/android/media/MediaConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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() { } Loading media/apex/java/android/media/MediaController2.java +105 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"); } Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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> Loading Loading @@ -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 Loading @@ -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) { Loading media/apex/java/android/media/MediaSession2.java +20 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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; Loading @@ -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<>(); } Loading Loading @@ -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 Loading media/apex/java/android/media/MediaSession2Service.java +55 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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<>(); Loading @@ -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); } } Loading Loading @@ -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 Loading Loading @@ -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} * Loading Loading @@ -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); Loading @@ -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 Loading
api/current.txt +9 −3 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); Loading Loading @@ -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(); Loading @@ -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";
media/apex/java/android/media/MediaConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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() { } Loading
media/apex/java/android/media/MediaController2.java +105 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"); } Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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> Loading Loading @@ -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 Loading @@ -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) { Loading
media/apex/java/android/media/MediaSession2.java +20 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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; Loading @@ -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<>(); } Loading Loading @@ -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 Loading
media/apex/java/android/media/MediaSession2Service.java +55 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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<>(); Loading @@ -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); } } Loading Loading @@ -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 Loading Loading @@ -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} * Loading Loading @@ -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); Loading @@ -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