Loading api/current.txt +17 −0 Original line number Diff line number Diff line Loading @@ -25912,6 +25912,23 @@ package android.media { method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); } public abstract class MediaSession2Service extends android.app.Service { ctor public MediaSession2Service(); 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.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"; } public static class MediaSession2Service.MediaNotification { ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification); method @NonNull public android.app.Notification getNotification(); method public int getNotificationId(); } public final class MediaSync { ctor public MediaSync(); method @NonNull public android.view.Surface createInputSurface(); media/java/android/media/MediaController2.java +10 −1 Original line number Diff line number Diff line Loading @@ -71,6 +71,8 @@ public class MediaController2 implements AutoCloseable { private final Object mLock = new Object(); //@GuardedBy("mLock") private boolean mClosed; //@GuardedBy("mLock") private int mNextSeqNumber; //@GuardedBy("mLock") private Session2Link mSessionBinder; Loading Loading @@ -141,7 +143,14 @@ public class MediaController2 implements AutoCloseable { @Override public void close() { synchronized (mLock) { if (mClosed) { // Already closed. Ignore rest of clean up code. // Note: unbindService() throws IllegalArgumentException when it's called twice. return; } mClosed = true; if (mServiceConnection != null) { // Note: This should be called even when the bindService() has returned false. mContext.unbindService(mServiceConnection); } if (mSessionBinder != null) { Loading @@ -167,7 +176,7 @@ public class MediaController2 implements AutoCloseable { * If it is not connected yet, it returns {@code null}. * <p> * This may differ with the {@link Session2Token} from the constructor. For example, if the * controller is created with the token for MediaSession2Service, this would return * controller is created with the token for {@link MediaSession2Service}, this would return * token for the {@link MediaSession2} in the service. * * @return Session2Token of the connected session, or {@code null} if not connected Loading media/java/android/media/MediaSession2.java +29 −16 Original line number Diff line number Diff line Loading @@ -90,6 +90,8 @@ public class MediaSession2 implements AutoCloseable { private boolean mClosed; //@GuardedBy("mLock") private boolean mPlaybackActive; //@GuardedBy("mLock") private ForegroundServiceEventCallback mForegroundServiceEventCallback; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) { Loading Loading @@ -119,6 +121,7 @@ public class MediaSession2 implements AutoCloseable { public void close() { try { List<ControllerInfo> controllerInfos; ForegroundServiceEventCallback callback; synchronized (mLock) { if (mClosed) { return; Loading @@ -126,11 +129,15 @@ public class MediaSession2 implements AutoCloseable { mClosed = true; controllerInfos = getConnectedControllers(); mConnectedControllers.clear(); mCallback.onSessionClosed(this); callback = mForegroundServiceEventCallback; mForegroundServiceEventCallback = null; } synchronized (MediaSession2.class) { SESSION_ID_LIST.remove(mSessionId); } if (callback != null) { callback.onSessionClosed(this); } for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } Loading Loading @@ -224,11 +231,16 @@ public class MediaSession2 implements AutoCloseable { * @param playbackActive {@code true} if the playback active, {@code false} otherwise. **/ public void setPlaybackActive(boolean playbackActive) { final ForegroundServiceEventCallback serviceCallback; synchronized (mLock) { if (mPlaybackActive == playbackActive) { return; } mPlaybackActive = playbackActive; serviceCallback = mForegroundServiceEventCallback; } if (serviceCallback != null) { serviceCallback.onPlaybackActiveChanged(this, playbackActive); } List<ControllerInfo> controllerInfos = getConnectedControllers(); for (ControllerInfo controller : controllerInfos) { Loading Loading @@ -257,6 +269,18 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { synchronized (mLock) { if (mForegroundServiceEventCallback == callback) { return; } if (mForegroundServiceEventCallback != null && callback != null) { throw new IllegalStateException("A session cannot be added to multiple services"); } mForegroundServiceEventCallback = callback; } } // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq, Bundle connectionRequest) { Loading Loading @@ -695,8 +719,6 @@ public class MediaSession2 implements AutoCloseable { * This API is not generally intended for third party application developers. */ public abstract static class SessionCallback { ForegroundServiceEventCallback mForegroundServiceEventCallback; /** * Called when a controller is created for this session. Return allowed commands for * controller. By default it returns {@code null}. Loading Loading @@ -753,19 +775,10 @@ public class MediaSession2 implements AutoCloseable { public void onCommandResult(@NonNull MediaSession2 session, @NonNull ControllerInfo controller, @NonNull Object token, @NonNull Session2Command command, @NonNull Session2Command.Result result) {} final void onSessionClosed(MediaSession2 session) { if (mForegroundServiceEventCallback != null) { mForegroundServiceEventCallback.onSessionClosed(session); } } void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { mForegroundServiceEventCallback = callback; } abstract static class ForegroundServiceEventCallback { public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {} public void onSessionClosed(MediaSession2 session) {} } } } media/java/android/media/MediaSession2Service.java +143 −28 Original line number Diff line number Diff line Loading @@ -19,7 +19,10 @@ package android.media; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Bundle; Loading @@ -28,8 +31,6 @@ import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; Loading @@ -42,11 +43,7 @@ import java.util.Map; * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. * @hide */ // TODO: Unhide // TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService() // when a session's player state becomes playing. public abstract class MediaSession2Service extends Service { /** * The {@link Intent} that must be declared as handled by the service. Loading @@ -56,10 +53,29 @@ public abstract class MediaSession2Service extends Service { private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback = new MediaSession2.ForegroundServiceEventCallback() { @Override public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive); } @Override public void onSessionClosed(MediaSession2 session) { removeSession(session); } }; private final Object mLock = new Object(); @GuardedBy("mLock") //@GuardedBy("mLock") private NotificationManager mNotificationManager; //@GuardedBy("mLock") private Intent mStartSelfIntent; //@GuardedBy("mLock") private Map<String, MediaSession2> mSessions = new ArrayMap<>(); //@GuardedBy("mLock") private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>(); //@GuardedBy("mLock") private MediaSession2ServiceStub mStub; /** Loading @@ -72,7 +88,12 @@ public abstract class MediaSession2Service extends Service { @Override public void onCreate() { super.onCreate(); synchronized (mLock) { mStub = new MediaSession2ServiceStub(this); mStartSelfIntent = new Intent(this, this.getClass()); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } } @CallSuper Loading @@ -80,16 +101,11 @@ public abstract class MediaSession2Service extends Service { @Nullable public IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } return null; } @CallSuper @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO: Dispatch media key events to the primary session. return START_STICKY; return null; } /** Loading @@ -104,10 +120,12 @@ public abstract class MediaSession2Service extends Service { public void onDestroy() { super.onDestroy(); synchronized (mLock) { for (MediaSession2 session : mSessions.values()) { session.getCallback().setForegroundServiceEventCallback(null); List<MediaSession2> sessions = getSessions(); for (MediaSession2 session : sessions) { removeSession(session); } mSessions.clear(); mNotifications.clear(); } mStub.close(); } Loading Loading @@ -143,6 +161,24 @@ public abstract class MediaSession2Service extends Service { @NonNull public abstract MediaSession2 onGetPrimarySession(); /** * Called when notification UI needs update. Override this method to show or cancel your own * notification UI. * <p> * This would be called on {@link MediaSession2}'s callback executor when playback state is * changed. * <p> * With the notification returned here, the service becomes foreground service when the playback * is started. Apps must request the permission * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes * background service after the playback is stopped. * * @param session a session that needs notification update. * @return a {@link MediaNotification}. Can be {@code null}. */ @Nullable public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); /** * Adds a session to this service. * <p> Loading @@ -161,21 +197,15 @@ public abstract class MediaSession2Service extends Service { } synchronized (mLock) { MediaSession2 previousSession = mSessions.get(session.getSessionId()); if (previousSession != session) { if (previousSession != null) { if (previousSession != session) { Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId() + ", previous=" + previousSession + ", session=" + session); } return; } mSessions.put(session.getSessionId(), session); session.getCallback().setForegroundServiceEventCallback( new MediaSession2.SessionCallback.ForegroundServiceEventCallback() { @Override public void onSessionClosed(MediaSession2 session) { removeSession(session); } }); session.setForegroundServiceEventCallback(mForegroundServiceEventCallback); } } Loading @@ -189,8 +219,21 @@ public abstract class MediaSession2Service extends Service { if (session == null) { throw new IllegalArgumentException("session shouldn't be null"); } MediaNotification notification; synchronized (mLock) { if (mSessions.get(session.getSessionId()) != session) { // Session isn't added or removed already. return; } mSessions.remove(session.getSessionId()); notification = mNotifications.remove(session); } session.setForegroundServiceEventCallback(null); if (notification != null) { mNotificationManager.cancel(notification.getNotificationId()); } if (getSessions().isEmpty()) { stopForeground(false); } } Loading @@ -207,6 +250,78 @@ public abstract class MediaSession2Service extends Service { return list; } /** * Called by registered {@link MediaSession2.ForegroundServiceEventCallback} * * @param session session with change * @param playbackActive {@code true} if playback is active. */ void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { MediaNotification mediaNotification = onUpdateNotification(session); if (mediaNotification == null) { // The service implementation doesn't want to use the automatic start/stopForeground // feature. return; } synchronized (mLock) { mNotifications.put(session, mediaNotification); } int id = mediaNotification.getNotificationId(); Notification notification = mediaNotification.getNotification(); if (!playbackActive) { mNotificationManager.notify(id, notification); return; } // playbackActive == true startForegroundService(mStartSelfIntent); startForeground(id, notification); } /** * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service * foreground service to keep playback running in the background. It's highly recommended to * show media style notification here. */ public static class MediaNotification { private final int mNotificationId; private final Notification mNotification; /** * Default constructor * * @param notificationId notification id to be used for * {@link NotificationManager#notify(int, Notification)}. * @param notification a notification to make session service run in the foreground. Media * style notification is recommended here. */ public MediaNotification(int notificationId, @NonNull Notification notification) { if (notification == null) { throw new IllegalArgumentException("notification shouldn't be null"); } mNotificationId = notificationId; mNotification = notification; } /** * Gets the id of the notification. * * @return the notification id */ public int getNotificationId() { return mNotificationId; } /** * Gets the notification. * * @return the notification */ @NonNull public Notification getNotification() { return mNotification; } } private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub implements AutoCloseable { final WeakReference<MediaSession2Service> mService; Loading media/java/android/media/Session2Token.java +3 −10 Original line number Diff line number Diff line Loading @@ -48,14 +48,6 @@ import java.util.Objects; * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ // New version of MediaSession2.Token for following reasons // - Stop implementing Parcelable for updatable support // - Represent session and library service (formerly browser service) in one class. // Previously MediaSession2.Token was for session and ComponentName was for service. // This helps controller apps to keep target of dispatching media key events in uniform way. // For details about the reason, see following. (Android O+) // android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged // TODO: use @link for MediaSession2Service public final class Session2Token implements Parcelable { private static final String TAG = "Session2Token"; Loading Loading @@ -85,12 +77,13 @@ public final class Session2Token implements Parcelable { public static final int TYPE_SESSION = 0; /** * Type for MediaSession2Service. * Type for {@link MediaSession2Service}. */ public static final int TYPE_SESSION_SERVICE = 1; private final int mUid; private final @TokenType int mType; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; private final Session2Link mSessionLink; Loading Loading
api/current.txt +17 −0 Original line number Diff line number Diff line Loading @@ -25912,6 +25912,23 @@ package android.media { method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); } public abstract class MediaSession2Service extends android.app.Service { ctor public MediaSession2Service(); 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.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"; } public static class MediaSession2Service.MediaNotification { ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification); method @NonNull public android.app.Notification getNotification(); method public int getNotificationId(); } public final class MediaSync { ctor public MediaSync(); method @NonNull public android.view.Surface createInputSurface();
media/java/android/media/MediaController2.java +10 −1 Original line number Diff line number Diff line Loading @@ -71,6 +71,8 @@ public class MediaController2 implements AutoCloseable { private final Object mLock = new Object(); //@GuardedBy("mLock") private boolean mClosed; //@GuardedBy("mLock") private int mNextSeqNumber; //@GuardedBy("mLock") private Session2Link mSessionBinder; Loading Loading @@ -141,7 +143,14 @@ public class MediaController2 implements AutoCloseable { @Override public void close() { synchronized (mLock) { if (mClosed) { // Already closed. Ignore rest of clean up code. // Note: unbindService() throws IllegalArgumentException when it's called twice. return; } mClosed = true; if (mServiceConnection != null) { // Note: This should be called even when the bindService() has returned false. mContext.unbindService(mServiceConnection); } if (mSessionBinder != null) { Loading @@ -167,7 +176,7 @@ public class MediaController2 implements AutoCloseable { * If it is not connected yet, it returns {@code null}. * <p> * This may differ with the {@link Session2Token} from the constructor. For example, if the * controller is created with the token for MediaSession2Service, this would return * controller is created with the token for {@link MediaSession2Service}, this would return * token for the {@link MediaSession2} in the service. * * @return Session2Token of the connected session, or {@code null} if not connected Loading
media/java/android/media/MediaSession2.java +29 −16 Original line number Diff line number Diff line Loading @@ -90,6 +90,8 @@ public class MediaSession2 implements AutoCloseable { private boolean mClosed; //@GuardedBy("mLock") private boolean mPlaybackActive; //@GuardedBy("mLock") private ForegroundServiceEventCallback mForegroundServiceEventCallback; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) { Loading Loading @@ -119,6 +121,7 @@ public class MediaSession2 implements AutoCloseable { public void close() { try { List<ControllerInfo> controllerInfos; ForegroundServiceEventCallback callback; synchronized (mLock) { if (mClosed) { return; Loading @@ -126,11 +129,15 @@ public class MediaSession2 implements AutoCloseable { mClosed = true; controllerInfos = getConnectedControllers(); mConnectedControllers.clear(); mCallback.onSessionClosed(this); callback = mForegroundServiceEventCallback; mForegroundServiceEventCallback = null; } synchronized (MediaSession2.class) { SESSION_ID_LIST.remove(mSessionId); } if (callback != null) { callback.onSessionClosed(this); } for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } Loading Loading @@ -224,11 +231,16 @@ public class MediaSession2 implements AutoCloseable { * @param playbackActive {@code true} if the playback active, {@code false} otherwise. **/ public void setPlaybackActive(boolean playbackActive) { final ForegroundServiceEventCallback serviceCallback; synchronized (mLock) { if (mPlaybackActive == playbackActive) { return; } mPlaybackActive = playbackActive; serviceCallback = mForegroundServiceEventCallback; } if (serviceCallback != null) { serviceCallback.onPlaybackActiveChanged(this, playbackActive); } List<ControllerInfo> controllerInfos = getConnectedControllers(); for (ControllerInfo controller : controllerInfos) { Loading Loading @@ -257,6 +269,18 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { synchronized (mLock) { if (mForegroundServiceEventCallback == callback) { return; } if (mForegroundServiceEventCallback != null && callback != null) { throw new IllegalStateException("A session cannot be added to multiple services"); } mForegroundServiceEventCallback = callback; } } // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq, Bundle connectionRequest) { Loading Loading @@ -695,8 +719,6 @@ public class MediaSession2 implements AutoCloseable { * This API is not generally intended for third party application developers. */ public abstract static class SessionCallback { ForegroundServiceEventCallback mForegroundServiceEventCallback; /** * Called when a controller is created for this session. Return allowed commands for * controller. By default it returns {@code null}. Loading Loading @@ -753,19 +775,10 @@ public class MediaSession2 implements AutoCloseable { public void onCommandResult(@NonNull MediaSession2 session, @NonNull ControllerInfo controller, @NonNull Object token, @NonNull Session2Command command, @NonNull Session2Command.Result result) {} final void onSessionClosed(MediaSession2 session) { if (mForegroundServiceEventCallback != null) { mForegroundServiceEventCallback.onSessionClosed(session); } } void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { mForegroundServiceEventCallback = callback; } abstract static class ForegroundServiceEventCallback { public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {} public void onSessionClosed(MediaSession2 session) {} } } }
media/java/android/media/MediaSession2Service.java +143 −28 Original line number Diff line number Diff line Loading @@ -19,7 +19,10 @@ package android.media; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Bundle; Loading @@ -28,8 +31,6 @@ import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; Loading @@ -42,11 +43,7 @@ import java.util.Map; * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. * @hide */ // TODO: Unhide // TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService() // when a session's player state becomes playing. public abstract class MediaSession2Service extends Service { /** * The {@link Intent} that must be declared as handled by the service. Loading @@ -56,10 +53,29 @@ public abstract class MediaSession2Service extends Service { private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback = new MediaSession2.ForegroundServiceEventCallback() { @Override public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive); } @Override public void onSessionClosed(MediaSession2 session) { removeSession(session); } }; private final Object mLock = new Object(); @GuardedBy("mLock") //@GuardedBy("mLock") private NotificationManager mNotificationManager; //@GuardedBy("mLock") private Intent mStartSelfIntent; //@GuardedBy("mLock") private Map<String, MediaSession2> mSessions = new ArrayMap<>(); //@GuardedBy("mLock") private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>(); //@GuardedBy("mLock") private MediaSession2ServiceStub mStub; /** Loading @@ -72,7 +88,12 @@ public abstract class MediaSession2Service extends Service { @Override public void onCreate() { super.onCreate(); synchronized (mLock) { mStub = new MediaSession2ServiceStub(this); mStartSelfIntent = new Intent(this, this.getClass()); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } } @CallSuper Loading @@ -80,16 +101,11 @@ public abstract class MediaSession2Service extends Service { @Nullable public IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } return null; } @CallSuper @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO: Dispatch media key events to the primary session. return START_STICKY; return null; } /** Loading @@ -104,10 +120,12 @@ public abstract class MediaSession2Service extends Service { public void onDestroy() { super.onDestroy(); synchronized (mLock) { for (MediaSession2 session : mSessions.values()) { session.getCallback().setForegroundServiceEventCallback(null); List<MediaSession2> sessions = getSessions(); for (MediaSession2 session : sessions) { removeSession(session); } mSessions.clear(); mNotifications.clear(); } mStub.close(); } Loading Loading @@ -143,6 +161,24 @@ public abstract class MediaSession2Service extends Service { @NonNull public abstract MediaSession2 onGetPrimarySession(); /** * Called when notification UI needs update. Override this method to show or cancel your own * notification UI. * <p> * This would be called on {@link MediaSession2}'s callback executor when playback state is * changed. * <p> * With the notification returned here, the service becomes foreground service when the playback * is started. Apps must request the permission * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes * background service after the playback is stopped. * * @param session a session that needs notification update. * @return a {@link MediaNotification}. Can be {@code null}. */ @Nullable public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); /** * Adds a session to this service. * <p> Loading @@ -161,21 +197,15 @@ public abstract class MediaSession2Service extends Service { } synchronized (mLock) { MediaSession2 previousSession = mSessions.get(session.getSessionId()); if (previousSession != session) { if (previousSession != null) { if (previousSession != session) { Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId() + ", previous=" + previousSession + ", session=" + session); } return; } mSessions.put(session.getSessionId(), session); session.getCallback().setForegroundServiceEventCallback( new MediaSession2.SessionCallback.ForegroundServiceEventCallback() { @Override public void onSessionClosed(MediaSession2 session) { removeSession(session); } }); session.setForegroundServiceEventCallback(mForegroundServiceEventCallback); } } Loading @@ -189,8 +219,21 @@ public abstract class MediaSession2Service extends Service { if (session == null) { throw new IllegalArgumentException("session shouldn't be null"); } MediaNotification notification; synchronized (mLock) { if (mSessions.get(session.getSessionId()) != session) { // Session isn't added or removed already. return; } mSessions.remove(session.getSessionId()); notification = mNotifications.remove(session); } session.setForegroundServiceEventCallback(null); if (notification != null) { mNotificationManager.cancel(notification.getNotificationId()); } if (getSessions().isEmpty()) { stopForeground(false); } } Loading @@ -207,6 +250,78 @@ public abstract class MediaSession2Service extends Service { return list; } /** * Called by registered {@link MediaSession2.ForegroundServiceEventCallback} * * @param session session with change * @param playbackActive {@code true} if playback is active. */ void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { MediaNotification mediaNotification = onUpdateNotification(session); if (mediaNotification == null) { // The service implementation doesn't want to use the automatic start/stopForeground // feature. return; } synchronized (mLock) { mNotifications.put(session, mediaNotification); } int id = mediaNotification.getNotificationId(); Notification notification = mediaNotification.getNotification(); if (!playbackActive) { mNotificationManager.notify(id, notification); return; } // playbackActive == true startForegroundService(mStartSelfIntent); startForeground(id, notification); } /** * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service * foreground service to keep playback running in the background. It's highly recommended to * show media style notification here. */ public static class MediaNotification { private final int mNotificationId; private final Notification mNotification; /** * Default constructor * * @param notificationId notification id to be used for * {@link NotificationManager#notify(int, Notification)}. * @param notification a notification to make session service run in the foreground. Media * style notification is recommended here. */ public MediaNotification(int notificationId, @NonNull Notification notification) { if (notification == null) { throw new IllegalArgumentException("notification shouldn't be null"); } mNotificationId = notificationId; mNotification = notification; } /** * Gets the id of the notification. * * @return the notification id */ public int getNotificationId() { return mNotificationId; } /** * Gets the notification. * * @return the notification */ @NonNull public Notification getNotification() { return mNotification; } } private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub implements AutoCloseable { final WeakReference<MediaSession2Service> mService; Loading
media/java/android/media/Session2Token.java +3 −10 Original line number Diff line number Diff line Loading @@ -48,14 +48,6 @@ import java.util.Objects; * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ // New version of MediaSession2.Token for following reasons // - Stop implementing Parcelable for updatable support // - Represent session and library service (formerly browser service) in one class. // Previously MediaSession2.Token was for session and ComponentName was for service. // This helps controller apps to keep target of dispatching media key events in uniform way. // For details about the reason, see following. (Android O+) // android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged // TODO: use @link for MediaSession2Service public final class Session2Token implements Parcelable { private static final String TAG = "Session2Token"; Loading Loading @@ -85,12 +77,13 @@ public final class Session2Token implements Parcelable { public static final int TYPE_SESSION = 0; /** * Type for MediaSession2Service. * Type for {@link MediaSession2Service}. */ public static final int TYPE_SESSION_SERVICE = 1; private final int mUid; private final @TokenType int mType; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; private final Session2Link mSessionLink; Loading