1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.Notification;
23 import android.app.Service;
24 import android.content.Intent;
25 import android.media.MediaSession2.ControllerInfo;
26 import android.media.update.ApiLoader;
27 import android.media.update.MediaSessionService2Provider;
28 import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
29 import android.os.IBinder;
30 
31 /**
32  * @hide
33  * Base class for media session services, which is the service version of the {@link MediaSession2}.
34  * <p>
35  * It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants
36  * to keep media playback in the background.
37  * <p>
38  * Here's the benefits of using {@link MediaSessionService2} instead of
39  * {@link MediaSession2}.
40  * <ul>
41  * <li>Another app can know that your app supports {@link MediaSession2} even when your app
42  * isn't running.
43  * <li>Another app can start playback of your app even when your app isn't running.
44  * </ul>
45  * For example, user's voice command can start playback of your app even when it's not running.
46  * <p>
47  * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
48  * <pre>
49  * &lt;service android:name="component_name_of_your_implementation" &gt;
50  *   &lt;intent-filter&gt;
51  *     &lt;action android:name="android.media.MediaSessionService2" /&gt;
52  *   &lt;/intent-filter&gt;
53  * &lt;/service&gt;</pre>
54  * <p>
55  * A {@link MediaSessionService2} is another form of {@link MediaSession2}. IDs shouldn't
56  * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
57  * default, an empty string will be used for ID of the service. If you want to specify an ID,
58  * declare metadata in the manifest as follows.
59  * <pre>
60  * &lt;service android:name="component_name_of_your_implementation" &gt;
61  *   &lt;intent-filter&gt;
62  *     &lt;action android:name="android.media.MediaSessionService2" /&gt;
63  *   &lt;/intent-filter&gt;
64  *   &lt;meta-data android:name="android.media.session"
65  *       android:value="session_id"/&gt;
66  * &lt;/service&gt;</pre>
67  * <p>
68  * It's recommended for an app to have a single {@link MediaSessionService2} declared in the
69  * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another
70  * app fails to pick the right session service when it wants to start the playback this app.
71  * <p>
72  * If there's conflicts with the session ID among the services, services wouldn't be available for
73  * any controllers.
74  * <p>
75  * Topic covered here:
76  * <ol>
77  * <li><a href="#ServiceLifecycle">Service Lifecycle</a>
78  * <li><a href="#Permissions">Permissions</a>
79  * </ol>
80  * <div class="special reference">
81  * <a name="ServiceLifecycle"></a>
82  * <h3>Service Lifecycle</h3>
83  * <p>
84  * Session service is bounded service. When a {@link MediaController2} is created for the
85  * session service, the controller binds to the session service. {@link #onCreateSession(String)}
86  * may be called after the {@link #onCreate} if the service hasn't created yet.
87  * <p>
88  * After the binding, session's {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}
89  *
90  * will be called to accept or reject connection request from a controller. If the connection is
91  * rejected, the controller will unbind. If it's accepted, the controller will be available to use
92  * and keep binding.
93  * <p>
94  * When playback is started for this session service, {@link #onUpdateNotification()}
95  * is called and service would become a foreground service. It's needed to keep playback after the
96  * controller is destroyed. The session service becomes background service when the playback is
97  * stopped.
98  * <a name="Permissions"></a>
99  * <h3>Permissions</h3>
100  * <p>
101  * Any app can bind to the session service with controller, but the controller can be used only if
102  * the session service accepted the connection request through
103  * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
104  */
105 public abstract class MediaSessionService2 extends Service {
106     private final MediaSessionService2Provider mProvider;
107 
108     /**
109      * This is the interface name that a service implementing a session service should say that it
110      * support -- that is, this is the action it uses for its intent filter.
111      */
112     public static final String SERVICE_INTERFACE = "android.media.MediaSessionService2";
113 
114     /**
115      * Name under which a MediaSessionService2 component publishes information about itself.
116      * This meta-data must provide a string value for the ID.
117      */
118     public static final String SERVICE_META_DATA = "android.media.session";
119 
MediaSessionService2()120     public MediaSessionService2() {
121         super();
122         mProvider = createProvider();
123     }
124 
createProvider()125     MediaSessionService2Provider createProvider() {
126         return ApiLoader.getProvider().createMediaSessionService2(this);
127     }
128 
129     /**
130      * Default implementation for {@link MediaSessionService2} to initialize session service.
131      * <p>
132      * Override this method if you need your own initialization. Derived classes MUST call through
133      * to the super class's implementation of this method.
134      */
135     @CallSuper
136     @Override
onCreate()137     public void onCreate() {
138         super.onCreate();
139         mProvider.onCreate_impl();
140     }
141 
142     /**
143      * Called when another app requested to start this service to get {@link MediaSession2}.
144      * <p>
145      * Session service will accept or reject the connection with the
146      * {@link MediaSession2.SessionCallback} in the created session.
147      * <p>
148      * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
149      * expected ID that you've specified through the AndroidManifest.xml.
150      * <p>
151      * This method will be called on the main thread.
152      *
153      * @param sessionId session id written in the AndroidManifest.xml.
154      * @return a new session
155      * @see MediaSession2.Builder
156      * @see #getSession()
157      */
onCreateSession(String sessionId)158     public @NonNull abstract MediaSession2 onCreateSession(String sessionId);
159 
160     /**
161      * Called when the playback state of this session is changed so notification needs update.
162      * Override this method to show or cancel your own notification UI.
163      * <p>
164      * With the notification returned here, the service become foreground service when the playback
165      * is started. It becomes background service after the playback is stopped.
166      *
167      * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
168      */
onUpdateNotification()169     public @Nullable MediaNotification onUpdateNotification() {
170         return mProvider.onUpdateNotification_impl();
171     }
172 
173     /**
174      * Get instance of the {@link MediaSession2} that you've previously created with the
175      * {@link #onCreateSession} for this service.
176      * <p>
177      * This may be {@code null} before the {@link #onCreate()} is finished.
178      *
179      * @return created session
180      */
getSession()181     public final @Nullable MediaSession2 getSession() {
182         return mProvider.getSession_impl();
183     }
184 
185     /**
186      * Default implementation for {@link MediaSessionService2} to handle incoming binding
187      * request. If the request is for getting the session, the intent will have action
188      * {@link #SERVICE_INTERFACE}.
189      * <p>
190      * Override this method if this service also needs to handle binder requests other than
191      * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's
192      * implementation of this method.
193      *
194      * @param intent
195      * @return Binder
196      */
197     @CallSuper
198     @Nullable
199     @Override
onBind(Intent intent)200     public IBinder onBind(Intent intent) {
201         return mProvider.onBind_impl(intent);
202     }
203 
204     /**
205      * Returned by {@link #onUpdateNotification()} for making session service foreground service
206      * to keep playback running in the background. It's highly recommended to show media style
207      * notification here.
208      */
209     public static class MediaNotification {
210         private final MediaNotificationProvider mProvider;
211 
212         /**
213          * Default constructor
214          *
215          * @param notificationId notification id to be used for
216          *      {@link android.app.NotificationManager#notify(int, Notification)}.
217          * @param notification a notification to make session service foreground service. Media
218          *      style notification is recommended here.
219          */
MediaNotification(int notificationId, @NonNull Notification notification)220         public MediaNotification(int notificationId, @NonNull Notification notification) {
221             mProvider = ApiLoader.getProvider().createMediaSessionService2MediaNotification(
222                     this, notificationId, notification);
223         }
224 
getNotificationId()225         public int getNotificationId() {
226             return mProvider.getNotificationId_impl();
227         }
228 
getNotification()229         public @NonNull Notification getNotification() {
230             return mProvider.getNotification_impl();
231         }
232     }
233 }
234