summaryrefslogtreecommitdiff
path: root/androidx/media/MediaSessionService2.java
diff options
context:
space:
mode:
Diffstat (limited to 'androidx/media/MediaSessionService2.java')
-rw-r--r--androidx/media/MediaSessionService2.java329
1 files changed, 329 insertions, 0 deletions
diff --git a/androidx/media/MediaSessionService2.java b/androidx/media/MediaSessionService2.java
new file mode 100644
index 00000000..7bad65c5
--- /dev/null
+++ b/androidx/media/MediaSessionService2.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2018 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 androidx.media;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
+import androidx.media.MediaSession2.ControllerInfo;
+import androidx.media.SessionToken2.TokenType;
+
+import java.util.List;
+
+/**
+ * @hide
+ * Base class for media session services, which is the service version of the {@link MediaSession2}.
+ * <p>
+ * It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants
+ * to keep media playback in the background.
+ * <p>
+ * Here's the benefits of using {@link MediaSessionService2} instead of
+ * {@link MediaSession2}.
+ * <ul>
+ * <li>Another app can know that your app supports {@link MediaSession2} even when your app
+ * isn't running.
+ * <li>Another app can start playback of your app even when your app isn't running.
+ * </ul>
+ * For example, user's voice command can start playback of your app even when it's not running.
+ * <p>
+ * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
+ * <pre>
+ * &lt;service android:name="component_name_of_your_implementation" &gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.media.MediaSessionService2" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;/service&gt;</pre>
+ * <p>
+ * A {@link MediaSessionService2} is another form of {@link MediaSession2}. IDs shouldn't
+ * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
+ * default, an empty string will be used for ID of the service. If you want to specify an ID,
+ * declare metadata in the manifest as follows.
+ * <pre>
+ * &lt;service android:name="component_name_of_your_implementation" &gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.media.MediaSessionService2" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;meta-data android:name="android.media.session"
+ * android:value="session_id"/&gt;
+ * &lt;/service&gt;</pre>
+ * <p>
+ * It's recommended for an app to have a single {@link MediaSessionService2} declared in the
+ * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another
+ * app fails to pick the right session service when it wants to start the playback this app.
+ * <p>
+ * If there's conflicts with the session ID among the services, services wouldn't be available for
+ * any controllers.
+ * <p>
+ * Topic covered here:
+ * <ol>
+ * <li><a href="#ServiceLifecycle">Service Lifecycle</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * </ol>
+ * <div class="special reference">
+ * <a name="ServiceLifecycle"></a>
+ * <h3>Service Lifecycle</h3>
+ * <p>
+ * Session service is bounded service. When a {@link MediaController2} is created for the
+ * session service, the controller binds to the session service. {@link #onCreateSession(String)}
+ * may be called after the {@link #onCreate} if the service hasn't created yet.
+ * <p>
+ * After the binding, session's
+ * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}
+ *
+ * will be called to accept or reject connection request from a controller. If the connection is
+ * rejected, the controller will unbind. If it's accepted, the controller will be available to use
+ * and keep binding.
+ * <p>
+ * When playback is started for this session service, {@link #onUpdateNotification()}
+ * is called and service would become a foreground service. It's needed to keep playback after the
+ * controller is destroyed. The session service becomes background service when the playback is
+ * stopped.
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * <p>
+ * Any app can bind to the session service with controller, but the controller can be used only if
+ * the session service accepted the connection request through
+ * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class MediaSessionService2 extends Service {
+ //private final MediaSessionService2Provider mProvider;
+
+ /**
+ * This is the interface name that a service implementing a session service should say that it
+ * support -- that is, this is the action it uses for its intent filter.
+ */
+ public static final String SERVICE_INTERFACE = "android.media.MediaSessionService2";
+
+ /**
+ * Name under which a MediaSessionService2 component publishes information about itself.
+ * This meta-data must provide a string value for the ID.
+ */
+ public static final String SERVICE_META_DATA = "android.media.session";
+
+ // Stub BrowserRoot for accepting any connction here.
+ // See MyBrowserService#onGetRoot() for detail.
+ static final BrowserRoot sDefaultBrowserRoot = new BrowserRoot(SERVICE_INTERFACE, null);
+
+ private final MediaBrowserServiceCompat mBrowserServiceCompat;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private NotificationManager mNotificationManager;
+ @GuardedBy("mLock")
+ private Intent mStartSelfIntent;
+ @GuardedBy("mLock")
+ private boolean mIsRunningForeground;
+ @GuardedBy("mLock")
+ private MediaSession2 mSession;
+
+ public MediaSessionService2() {
+ super();
+ mBrowserServiceCompat = createBrowserServiceCompat();
+ }
+
+ MediaBrowserServiceCompat createBrowserServiceCompat() {
+ return new MyBrowserService();
+ }
+
+ /**
+ * Default implementation for {@link MediaSessionService2} to initialize session service.
+ * <p>
+ * 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();
+ mBrowserServiceCompat.attachToBaseContext(this);
+ mBrowserServiceCompat.onCreate();
+ SessionToken2 token = new SessionToken2(this,
+ new ComponentName(getPackageName(), getClass().getName()));
+ if (token.getType() != getSessionType()) {
+ throw new RuntimeException("Expected session type " + getSessionType()
+ + " but was " + token.getType());
+ }
+ MediaSession2 session = onCreateSession(token.getId());
+ synchronized (mLock) {
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mStartSelfIntent = new Intent(this, getClass());
+ mSession = session;
+ if (mSession == null || !token.getId().equals(mSession.getToken().getId())) {
+ throw new RuntimeException("Expected session with id " + token.getId()
+ + ", but got " + mSession);
+ }
+ mBrowserServiceCompat.setSessionToken(mSession.getToken().getSessionCompatToken());
+ }
+ }
+
+ @TokenType int getSessionType() {
+ return SessionToken2.TYPE_SESSION_SERVICE;
+ }
+
+ /**
+ * Called when another app requested to start this service to get {@link MediaSession2}.
+ * <p>
+ * Session service will accept or reject the connection with the
+ * {@link MediaSession2.SessionCallback} in the created session.
+ * <p>
+ * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
+ * expected ID that you've specified through the AndroidManifest.xml.
+ * <p>
+ * This method will be called on the main thread.
+ *
+ * @param sessionId session id written in the AndroidManifest.xml.
+ * @return a new session
+ * @see MediaSession2.Builder
+ * @see #getSession()
+ */
+ public @NonNull abstract MediaSession2 onCreateSession(String sessionId);
+
+ /**
+ * Called when the playback state of this session is changed so notification needs update.
+ * Override this method to show or cancel your own notification UI.
+ * <p>
+ * With the notification returned here, the service become foreground service when the playback
+ * is started. It becomes background service after the playback is stopped.
+ *
+ * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
+ */
+ public @Nullable MediaNotification onUpdateNotification() {
+ return null;
+ }
+
+ /**
+ * Get instance of the {@link MediaSession2} that you've previously created with the
+ * {@link #onCreateSession} for this service.
+ * <p>
+ * This may be {@code null} before the {@link #onCreate()} is finished.
+ *
+ * @return created session
+ */
+ public final @Nullable MediaSession2 getSession() {
+ synchronized (mLock) {
+ return mSession;
+ }
+ }
+
+ /**
+ * Default implementation for {@link MediaSessionService2} to handle incoming binding
+ * request. If the request is for getting the session, the intent will have action
+ * {@link #SERVICE_INTERFACE}.
+ * <p>
+ * Override this method if this service also needs to handle binder requests other than
+ * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's
+ * implementation of this method.
+ *
+ * @param intent
+ * @return Binder
+ */
+ @CallSuper
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())
+ || MediaBrowserServiceCompat.SERVICE_INTERFACE.equals(intent.getAction())) {
+ // Change the intent action for browser service.
+ Intent browserServiceIntent = new Intent(intent);
+ browserServiceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ return mBrowserServiceCompat.onBind(intent);
+ }
+ return null;
+ }
+
+ MediaBrowserServiceCompat getServiceCompat() {
+ return mBrowserServiceCompat;
+ }
+
+ /**
+ * Returned by {@link #onUpdateNotification()} 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 foreground service. 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 id.
+ *
+ * @return the notification id
+ */
+ public int getNotificationId() {
+ return mNotificationId;
+ }
+
+ /**
+ * Gets the notification.
+ *
+ * @return the notification
+ */
+ public @NonNull Notification getNotification() {
+ return mNotification;
+ }
+ }
+
+ private static class MyBrowserService extends MediaBrowserServiceCompat {
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ // Returns *stub* root here. Here's the reason.
+ // 1. A non-null BrowserRoot should be returned here to keep the binding
+ // 2. MediaSessionService2 is defined as the simplified version of the library
+ // service with no browsing feature, so shouldn't allow MediaBrowserServiceCompat
+ // specific operations.
+ // TODO: Revisit here API not to return stub root here. The fake media ID here may be
+ // used by the browser service for real.
+ return sDefaultBrowserRoot;
+ }
+
+ @Override
+ public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
+ // Disallow loading children.
+ }
+ }
+}