/* * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; import android.app.Notification; 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; import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * This API is not generally intended for third party application developers. * Use the AndroidX * Media2 session * Library for consistent behavior across all devices. *
 * Service containing {@link MediaSession2}.
 */
public abstract class MediaSession2Service extends Service {
    /**
     * The {@link Intent} that must be declared as handled by the service.
     */
    public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
    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")
    private NotificationManager mNotificationManager;
    //@GuardedBy("mLock")
    private MediaSessionManager mMediaSessionManager;
    //@GuardedBy("mLock")
    private Intent mStartSelfIntent;
    //@GuardedBy("mLock")
    private Map 
     * Override this method if you need your own initialization. Derived classes MUST call through
     * to the super class's implementation of this method.
     */
    @CallSuper
    @Override
    public void onCreate() {
        super.onCreate();
        synchronized (mLock) {
            mStub = new MediaSession2ServiceStub(this);
            mStartSelfIntent = new Intent(this, this.getClass());
            mNotificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            mMediaSessionManager =
                    (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
    }
    @CallSuper
    @Override
    @Nullable
    public IBinder onBind(@NonNull Intent intent) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
            synchronized (mLock) {
                return mStub;
            }
        }
        return null;
    }
    /**
     * Called by the system to notify that it is no longer used and is being removed. Do not call
     * this method directly.
     *  
     * Override this method if you need your own clean up. Derived classes MUST call through
     * to the super class's implementation of this method.
     */
    @CallSuper
    @Override
    public void onDestroy() {
        super.onDestroy();
        synchronized (mLock) {
            List 
     * Session returned here will be added to this service automatically. You don't need to call
     * {@link #addSession(MediaSession2)} for that.
     *  
     * This method is always called on the main thread.
     *
     * @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()
     */
    @Nullable
    public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo);
    /**
     * Called to update the media notification when the playback state changes.
     *  
     * If playback is active and a notification is returned, the service uses it to become a
     * foreground service. If playback is not active then the notification is still posted, but the
     * service does not become a foreground service.
     *  
     * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission
     * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU}
     * or later, notifications will only be posted if the app has also been granted the
     * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission.
     *
     * @param session the session for which an updated media notification is required.
     * @return the {@link MediaNotification}. Can be {@code null}.
     */
    @Nullable
    public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
    /**
     * Adds a session to this service.
     *  
     * Added session will be removed automatically when it's closed, or removed when
     * {@link #removeSession} is called.
     *
     * @param session a session to be added.
     * @see #removeSession(MediaSession2)
     */
    public final void addSession(@NonNull MediaSession2 session) {
        if (session == null) {
            throw new IllegalArgumentException("session shouldn't be null");
        }
        if (session.isClosed()) {
            throw new IllegalArgumentException("session is already closed");
        }
        synchronized (mLock) {
            MediaSession2 previousSession = mSessions.get(session.getId());
            if (previousSession != null) {
                if (previousSession != session) {
                    Log.w(TAG, "Session ID should be unique, ID=" + session.getId()
                            + ", previous=" + previousSession + ", session=" + session);
                }
                return;
            }
            mSessions.put(session.getId(), session);
            session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
        }
    }
    /**
     * Removes a session from this service.
     *
     * @param session a session to be removed.
     * @see #addSession(MediaSession2)
     */
    public final void removeSession(@NonNull MediaSession2 session) {
        if (session == null) {
            throw new IllegalArgumentException("session shouldn't be null");
        }
        MediaNotification notification;
        synchronized (mLock) {
            if (mSessions.get(session.getId()) != session) {
                // Session isn't added or removed already.
                return;
            }
            mSessions.remove(session.getId());
            notification = mNotifications.remove(session);
        }
        session.setForegroundServiceEventCallback(null);
        if (notification != null) {
            mNotificationManager.cancel(notification.getNotificationId());
        }
        if (getSessions().isEmpty()) {
            stopForeground(false);
        }
    }
    /**
     * Gets the list of {@link MediaSession2}s that you've added to this service.
     *
     * @return sessions
     */
    public final @NonNull List 
     * 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