Skip to content

Most visited

Recently visited

navigation
MediaBrowserService / src / com.example.android.mediabrowserservice /

MusicService.java

1
 /*
2
 * Copyright (C) 2014 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 com.example.android.mediabrowserservice;
18
 
19
 import android.app.PendingIntent;
20
 import android.content.Context;
21
 import android.content.Intent;
22
 import android.graphics.Bitmap;
23
 import android.media.MediaDescription;
24
 import android.media.MediaMetadata;
25
 import android.media.browse.MediaBrowser.MediaItem;
26
 import android.media.session.MediaSession;
27
 import android.media.session.PlaybackState;
28
 import android.net.Uri;
29
 import android.os.Bundle;
30
 import android.os.Handler;
31
 import android.os.Message;
32
 import android.os.SystemClock;
33
 import android.service.media.MediaBrowserService;
34
 import android.text.TextUtils;
35
 
36
 import com.example.android.mediabrowserservice.model.MusicProvider;
37
 import com.example.android.mediabrowserservice.utils.CarHelper;
38
 import com.example.android.mediabrowserservice.utils.LogHelper;
39
 import com.example.android.mediabrowserservice.utils.MediaIDHelper;
40
 import com.example.android.mediabrowserservice.utils.QueueHelper;
41
 
42
 import java.lang.ref.WeakReference;
43
 import java.util.ArrayList;
44
 import java.util.Collections;
45
 import java.util.List;
46
 
47
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE;
48
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.MEDIA_ID_ROOT;
49
 import static com.example.android.mediabrowserservice.utils.MediaIDHelper.createBrowseCategoryMediaID;
50
 
51
 /**
52
  * This class provides a MediaBrowser through a service. It exposes the media library to a browsing
53
  * client, through the onGetRoot and onLoadChildren methods. It also creates a MediaSession and
54
  * exposes it through its MediaSession.Token, which allows the client to create a MediaController
55
  * that connects to and send control commands to the MediaSession remotely. This is useful for
56
  * user interfaces that need to interact with your media session, like Android Auto. You can
57
  * (should) also use the same service from your app's UI, which gives a seamless playback
58
  * experience to the user.
59
  *
60
  * To implement a MediaBrowserService, you need to:
61
  *
62
  * <ul>
63
  *
64
  * <li> Extend {@link android.service.media.MediaBrowserService}, implementing the media browsing
65
  *      related methods {@link android.service.media.MediaBrowserService#onGetRoot} and
66
  *      {@link android.service.media.MediaBrowserService#onLoadChildren};
67
  * <li> In onCreate, start a new {@link android.media.session.MediaSession} and notify its parent
68
  *      with the session's token {@link android.service.media.MediaBrowserService#setSessionToken};
69
  *
70
  * <li> Set a callback on the
71
  *      {@link android.media.session.MediaSession#setCallback(android.media.session.MediaSession.Callback)}.
72
  *      The callback will receive all the user's actions, like play, pause, etc;
73
  *
74
  * <li> Handle all the actual music playing using any method your app prefers (for example,
75
  *      {@link android.media.MediaPlayer})
76
  *
77
  * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods
78
  *      {@link android.media.session.MediaSession#setPlaybackState(android.media.session.PlaybackState)}
79
  *      {@link android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)} and
80
  *      {@link android.media.session.MediaSession#setQueue(java.util.List)})
81
  *
82
  * <li> Declare and export the service in AndroidManifest with an intent receiver for the action
83
  *      android.media.browse.MediaBrowserService
84
  *
85
  * </ul>
86
  *
87
  * To make your app compatible with Android Auto, you also need to:
88
  *
89
  * <ul>
90
  *
91
  * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource
92
  *      with a &lt;automotiveApp&gt; root element. For a media app, this must include
93
  *      an &lt;uses name="media"/&gt; element as a child.
94
  *      For example, in AndroidManifest.xml:
95
  *          &lt;meta-data android:name="com.google.android.gms.car.application"
96
  *              android:resource="@xml/automotive_app_desc"/&gt;
97
  *      And in res/values/automotive_app_desc.xml:
98
  *          &lt;automotiveApp&gt;
99
  *              &lt;uses name="media"/&gt;
100
  *          &lt;/automotiveApp&gt;
101
  *
102
  * </ul>
103
 
104
  * @see <a href="README.md">README.md</a> for more details.
105
  *
106
  */
107
 
108
 public class MusicService extends MediaBrowserService implements Playback.Callback {
109
 
110
     // The action of the incoming Intent indicating that it contains a command
111
     // to be executed (see {@link #onStartCommand})
112
     public static final String ACTION_CMD = "com.example.android.mediabrowserservice.ACTION_CMD";
113
     // The key in the extras of the incoming Intent indicating the command that
114
     // should be executed (see {@link #onStartCommand})
115
     public static final String CMD_NAME = "CMD_NAME";
116
     // A value of a CMD_NAME key in the extras of the incoming Intent that
117
     // indicates that the music playback should be paused (see {@link #onStartCommand})
118
     public static final String CMD_PAUSE = "CMD_PAUSE";
119
 
120
     private static final String TAG = LogHelper.makeLogTag(MusicService.class);
121
     // Action to thumbs up a media item
122
     private static final String CUSTOM_ACTION_THUMBS_UP =
123
         "com.example.android.mediabrowserservice.THUMBS_UP";
124
     // Delay stopSelf by using a handler.
125
     private static final int STOP_DELAY = 30000;
126
 
127
     // Music catalog manager
128
     private MusicProvider mMusicProvider;
129
     private MediaSession mSession;
130
     // "Now playing" queue:
131
     private List<MediaSession.QueueItem> mPlayingQueue;
132
     private int mCurrentIndexOnQueue;
133
     private MediaNotificationManager mMediaNotificationManager;
134
     // Indicates whether the service was started.
135
     private boolean mServiceStarted;
136
     private DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this);
137
     private Playback mPlayback;
138
     private PackageValidator mPackageValidator;
139
 
140
     /*
141
      * (non-Javadoc)
142
      * @see android.app.Service#onCreate()
143
      */
144
     @Override
145
     public void onCreate() {
146
         super.onCreate();
147
         LogHelper.d(TAG, "onCreate");
148
 
149
         mPlayingQueue = new ArrayList<>();
150
         mMusicProvider = new MusicProvider();
151
         mPackageValidator = new PackageValidator(this);
152
 
153
         // Start a new MediaSession
154
         mSession = new MediaSession(this, "MusicService");
155
         setSessionToken(mSession.getSessionToken());
156
         mSession.setCallback(new MediaSessionCallback());
157
         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
158
             MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
159
 
160
         mPlayback = new Playback(this, mMusicProvider);
161
         mPlayback.setState(PlaybackState.STATE_NONE);
162
         mPlayback.setCallback(this);
163
         mPlayback.start();
164
 
165
         Context context = getApplicationContext();
166
         Intent intent = new Intent(context, MusicPlayerActivity.class);
167
         PendingIntent pi = PendingIntent.getActivity(context, 99 /*request code*/,
168
                 intent, PendingIntent.FLAG_UPDATE_CURRENT);
169
         mSession.setSessionActivity(pi);
170
 
171
         Bundle extras = new Bundle();
172
         CarHelper.setSlotReservationFlags(extras, true, true, true);
173
         mSession.setExtras(extras);
174
 
175
         updatePlaybackState(null);
176
 
177
         mMediaNotificationManager = new MediaNotificationManager(this);
178
     }
179
 
180
     /**
181
      * (non-Javadoc)
182
      * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
183
      */
184
     @Override
185
     public int onStartCommand(Intent startIntent, int flags, int startId) {
186
         if (startIntent != null) {
187
             String action = startIntent.getAction();
188
             String command = startIntent.getStringExtra(CMD_NAME);
189
             if (ACTION_CMD.equals(action)) {
190
                 if (CMD_PAUSE.equals(command)) {
191
                     if (mPlayback != null && mPlayback.isPlaying()) {
192
                         handlePauseRequest();
193
                     }
194
                 }
195
             }
196
         }
197
         return START_STICKY;
198
     }
199
 
200
     /**
201
      * (non-Javadoc)
202
      * @see android.app.Service#onDestroy()
203
      */
204
     @Override
205
     public void onDestroy() {
206
         LogHelper.d(TAG, "onDestroy");
207
         // Service is being killed, so make sure we release our resources
208
         handleStopRequest(null);
209
 
210
         mDelayedStopHandler.removeCallbacksAndMessages(null);
211
         // Always release the MediaSession to clean up resources
212
         // and notify associated MediaController(s).
213
         mSession.release();
214
     }
215
 
216
     @Override
217
     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
218
         LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName,
219
                 "; clientUid=" + clientUid + " ; rootHints=", rootHints);
220
         // To ensure you are not allowing any arbitrary app to browse your app's contents, you
221
         // need to check the origin:
222
         if (!mPackageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
223
             // If the request comes from an untrusted package, return null. No further calls will
224
             // be made to other media browsing methods.
225
             LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package "
226
                     + clientPackageName);
227
             return null;
228
         }
229
         //noinspection StatementWithEmptyBody
230
         if (CarHelper.isValidCarPackage(clientPackageName)) {
231
             // Optional: if your app needs to adapt ads, music library or anything else that
232
             // needs to run differently when connected to the car, this is where you should handle
233
             // it.
234
         }
235
         return new BrowserRoot(MEDIA_ID_ROOT, null);
236
     }
237
 
238
     @Override
239
     public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
240
         if (!mMusicProvider.isInitialized()) {
241
             // Use result.detach to allow calling result.sendResult from another thread:
242
             result.detach();
243
 
244
             mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() {
245
                 @Override
246
                 public void onMusicCatalogReady(boolean success) {
247
                     if (success) {
248
                         loadChildrenImpl(parentMediaId, result);
249
                     } else {
250
                         updatePlaybackState(getString(R.string.error_no_metadata));
251
                         result.sendResult(Collections.<MediaItem>emptyList());
252
                     }
253
                 }
254
             });
255
 
256
         } else {
257
             // If our music catalog is already loaded/cached, load them into result immediately
258
             loadChildrenImpl(parentMediaId, result);
259
         }
260
     }
261
 
262
     /**
263
      * Actual implementation of onLoadChildren that assumes that MusicProvider is already
264
      * initialized.
265
      */
266
     private void loadChildrenImpl(final String parentMediaId,
267
                                   final Result<List<MediaItem>> result) {
268
         LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId);
269
 
270
         List<MediaItem> mediaItems = new ArrayList<>();
271
 
272
         if (MEDIA_ID_ROOT.equals(parentMediaId)) {
273
             LogHelper.d(TAG, "OnLoadChildren.ROOT");
274
             mediaItems.add(new MediaItem(
275
                     new MediaDescription.Builder()
276
                         .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
277
                         .setTitle(getString(R.string.browse_genres))
278
                         .setIconUri(Uri.parse("android.resource://" +
279
                             "com.example.android.mediabrowserservice/drawable/ic_by_genre"))
280
                         .setSubtitle(getString(R.string.browse_genre_subtitle))
281
                         .build(), MediaItem.FLAG_BROWSABLE
282
             ));
283
 
284
         } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) {
285
             LogHelper.d(TAG, "OnLoadChildren.GENRES");
286
             for (String genre : mMusicProvider.getGenres()) {
287
                 MediaItem item = new MediaItem(
288
                     new MediaDescription.Builder()
289
                         .setMediaId(createBrowseCategoryMediaID(MEDIA_