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
17package android.media;
18
19import android.annotation.CallSuper;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.Notification;
23import android.app.Service;
24import android.content.Intent;
25import android.media.MediaSession2.ControllerInfo;
26import android.media.update.ApiLoader;
27import android.media.update.MediaSessionService2Provider;
28import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
29import 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 */
105public 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
120    public MediaSessionService2() {
121        super();
122        mProvider = createProvider();
123    }
124
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
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     */
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     */
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     */
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
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         */
220        public MediaNotification(int notificationId, @NonNull Notification notification) {
221            mProvider = ApiLoader.getProvider().createMediaSessionService2MediaNotification(
222                    this, notificationId, notification);
223        }
224
225        public int getNotificationId() {
226            return mProvider.getNotificationId_impl();
227        }
228
229        public @NonNull Notification getNotification() {
230            return mProvider.getNotification_impl();
231        }
232    }
233}
234