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

Commit ca626b63 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "unhide_mss"

* changes:
  MediaSession2Service: Unhide
  MediaSession2Service: Add onUpdateNotification()
parents f1cef973 70c8cb1d
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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();
+10 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
@@ -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
+29 −16
Original line number Diff line number Diff line
@@ -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) {
@@ -119,6 +121,7 @@ public class MediaSession2 implements AutoCloseable {
    public void close() {
        try {
            List<ControllerInfo> controllerInfos;
            ForegroundServiceEventCallback callback;
            synchronized (mLock) {
                if (mClosed) {
                    return;
@@ -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();
            }
@@ -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) {
@@ -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) {
@@ -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}.
@@ -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) {}
    }
}
}
+143 −28
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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.
@@ -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;

    /**
@@ -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
@@ -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;
    }

    /**
@@ -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();
    }
@@ -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>
@@ -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);
        }
    }

@@ -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);
        }
    }

@@ -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;
+3 −10
Original line number Diff line number Diff line
@@ -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";

@@ -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;