1a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim/* 2a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* Copyright (C) 2015 The Android Open Source Project 3a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* 4a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* Licensed under the Apache License, Version 2.0 (the "License"); 5a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* you may not use this file except in compliance with the License. 6a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* You may obtain a copy of the License at 7a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* 8a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* http://www.apache.org/licenses/LICENSE-2.0 9a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* 10a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* Unless required by applicable law or agreed to in writing, software 11a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* distributed under the License is distributed on an "AS IS" BASIS, 12a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* See the License for the specific language governing permissions and 14a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim* limitations under the License. 15a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim*/ 16a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 17a907614755847b2630561a1e5949b2b416600d97Sungsoo Limpackage com.example.android.supportv4.media; 18a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 19ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viveretteimport static com.example.android.supportv4.media.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE; 20ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viveretteimport static com.example.android.supportv4.media.utils.MediaIDHelper.MEDIA_ID_ROOT; 21ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viveretteimport static com.example.android.supportv4.media.utils.MediaIDHelper.createBrowseCategoryMediaID; 22ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viverette 23a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.app.PendingIntent; 24a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.content.Context; 25a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.content.Intent; 26a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.graphics.Bitmap; 27a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.net.Uri; 28a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.os.Bundle; 29a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.os.Handler; 30a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.os.Message; 31a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.os.SystemClock; 323ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moonimport android.support.v4.media.MediaBrowserCompat; 33a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.support.v4.media.MediaBrowserCompat.MediaItem; 34a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.support.v4.media.MediaBrowserServiceCompat; 35ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viveretteimport android.support.v4.media.MediaDescriptionCompat; 36ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viveretteimport android.support.v4.media.MediaMetadataCompat; 37e02d0df590b7031c64202b8aa6e1cb1ba6ba3090Donghyun Choimport android.support.v4.media.session.MediaButtonReceiver; 38a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.support.v4.media.session.MediaSessionCompat; 39a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.support.v4.media.session.PlaybackStateCompat; 40a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.text.TextUtils; 41a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.util.Log; 42a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 43a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.R; 44a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.media.model.MusicProvider; 45a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.media.utils.CarHelper; 46a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.media.utils.MediaIDHelper; 47a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.media.utils.QueueHelper; 48a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 49a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport java.lang.ref.WeakReference; 50a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport java.util.ArrayList; 51a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport java.util.Collections; 52a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport java.util.List; 53a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 54a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim/** 55a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * This class provides a MediaBrowser through a service. It exposes the media library to a browsing 56a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * client, through the onGetRoot and onLoadChildren methods. It also creates a MediaSession and 57a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * exposes it through its MediaSession.Token, which allows the client to create a MediaController 58a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * that connects to and send control commands to the MediaSession remotely. This is useful for 59a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * user interfaces that need to interact with your media session, like Android Auto. You can 60a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * (should) also use the same service from your app's UI, which gives a seamless playback 61a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * experience to the user. 62a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 63a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * To implement a MediaBrowserService, you need to: 64a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 65a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <ul> 66a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 67a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Extend {@link android.service.media.MediaBrowserService}, implementing the media browsing 68a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * related methods {@link android.service.media.MediaBrowserService#onGetRoot} and 69a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.service.media.MediaBrowserService#onLoadChildren}; 70a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> In onCreate, start a new {@link android.media.session.MediaSession} and notify its parent 71a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * with the session's token {@link android.service.media.MediaBrowserService#setSessionToken}; 72a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 73a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Set a callback on the 74a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.media.session.MediaSession#setCallback(android.media.session.MediaSession.Callback)}. 75a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * The callback will receive all the user's actions, like play, pause, etc; 76a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 77a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Handle all the actual music playing using any method your app prefers (for example, 78a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.media.MediaPlayer}) 79a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 80a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods 81a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.media.session.MediaSession#setPlaybackState(android.media.session.PlaybackState)} 82a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)} and 83a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * {@link android.media.session.MediaSession#setQueue(java.util.List)}) 84a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 85a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Declare and export the service in AndroidManifest with an intent receiver for the action 86a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * android.media.browse.MediaBrowserService 87a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 88a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * </ul> 89a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 90a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * To make your app compatible with Android Auto, you also need to: 91a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 92a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <ul> 93a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 94a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource 95a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * with a <automotiveApp> root element. For a media app, this must include 96a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * an <uses name="media"/> element as a child. 97a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * For example, in AndroidManifest.xml: 98a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <meta-data android:name="com.google.android.gms.car.application" 99a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * android:resource="@xml/automotive_app_desc"/> 100a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * And in res/values/automotive_app_desc.xml: 101a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <automotiveApp> 102a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <uses name="media"/> 103a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * </automotiveApp> 104a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * <p/> 105a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * </ul> 106a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * 107a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * @see <a href="README.md">README.md</a> for more details. 108a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 109a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 110a907614755847b2630561a1e5949b2b416600d97Sungsoo Limpublic class MediaBrowserServiceSupport extends MediaBrowserServiceCompat 111a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim implements Playback.Callback { 112a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 113a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // The action of the incoming Intent indicating that it contains a command 114a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // to be executed (see {@link #onStartCommand}) 115a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public static final String ACTION_CMD = "com.example.android.supportv4.media.ACTION_CMD"; 116a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // The key in the extras of the incoming Intent indicating the command that 117a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // should be executed (see {@link #onStartCommand}) 118a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public static final String CMD_NAME = "CMD_NAME"; 119a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // A value of a CMD_NAME key in the extras of the incoming Intent that 120a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // indicates that the music playback should be paused (see {@link #onStartCommand}) 121a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public static final String CMD_PAUSE = "CMD_PAUSE"; 122ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viverette // Log tag must be <= 23 characters, so truncate class name. 123ce390fcb9066a8a8d0a1cad3be6f78a2ec4dad67Alan Viverette private static final String TAG = "MediaBrowserService"; 124a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Action to thumbs up a media item 125a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private static final String CUSTOM_ACTION_THUMBS_UP = 126a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim "com.example.android.supportv4.media.THUMBS_UP"; 127a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Delay stopSelf by using a handler. 128a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private static final int STOP_DELAY = 30000; 129a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 130a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Music catalog manager 131a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private MusicProvider mMusicProvider; 132a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private MediaSessionCompat mSession; 133a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // "Now playing" queue: 134a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private List<MediaSessionCompat.QueueItem> mPlayingQueue; 135a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private int mCurrentIndexOnQueue; 136a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private MediaNotificationManager mMediaNotificationManager; 137a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Indicates whether the service was started. 138a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private boolean mServiceStarted; 139a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this); 140a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private Playback mPlayback; 141a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private PackageValidator mPackageValidator; 142a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 143a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /* 144a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * (non-Javadoc) 145a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * @see android.app.Service#onCreate() 146a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 147a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 148a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onCreate() { 149a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim super.onCreate(); 150a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "onCreate"); 151a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 152a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayingQueue = new ArrayList<>(); 153a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMusicProvider = new MusicProvider(); 154a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPackageValidator = new PackageValidator(this); 155a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 156a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Start a new MediaSession 157a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession = new MediaSessionCompat(this, "MusicService"); 158a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim setSessionToken(mSession.getSessionToken()); 159a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setCallback(new MediaSessionCallback()); 160a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | 161a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); 162a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 163a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback = new Playback(this, mMusicProvider); 164a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.setState(PlaybackStateCompat.STATE_NONE); 165a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.setCallback(this); 166a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.start(); 167a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 168a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Context context = getApplicationContext(); 169a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Intent intent = new Intent(context, MediaBrowserSupport.class); 170a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim PendingIntent pi = PendingIntent.getActivity(context, 99 /*request code*/, 171a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim intent, PendingIntent.FLAG_UPDATE_CURRENT); 172a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setSessionActivity(pi); 173a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 174a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Bundle extras = new Bundle(); 175a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim CarHelper.setSlotReservationFlags(extras, true, true, true); 176a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setExtras(extras); 177a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 178a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(null); 179a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 180a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMediaNotificationManager = new MediaNotificationManager(this); 181a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 182a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 183a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 184a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * (non-Javadoc) 185a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * 186a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * @see android.app.Service#onStartCommand(android.content.Intent, int, int) 187a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 188a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 189a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public int onStartCommand(Intent startIntent, int flags, int startId) { 190a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (startIntent != null) { 191a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String action = startIntent.getAction(); 192e02d0df590b7031c64202b8aa6e1cb1ba6ba3090Donghyun Cho if (Intent.ACTION_MEDIA_BUTTON.equals(action)) { 193e02d0df590b7031c64202b8aa6e1cb1ba6ba3090Donghyun Cho MediaButtonReceiver.handleIntent(mSession, startIntent); 194e02d0df590b7031c64202b8aa6e1cb1ba6ba3090Donghyun Cho } else if (ACTION_CMD.equals(action)) { 195e02d0df590b7031c64202b8aa6e1cb1ba6ba3090Donghyun Cho if (CMD_PAUSE.equals(startIntent.getStringExtra(CMD_NAME))) { 196a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayback != null && mPlayback.isPlaying()) { 197a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePauseRequest(); 198a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 199a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 200a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 201a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 202a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return START_STICKY; 203a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 204a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 205a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 206a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * (non-Javadoc) 207a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * 208a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * @see android.app.Service#onDestroy() 209a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 210a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 211a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onDestroy() { 212a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "onDestroy"); 213a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Service is being killed, so make sure we release our resources 214a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest(null); 215a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 216a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.removeCallbacksAndMessages(null); 217a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Always release the MediaSession to clean up resources 218a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // and notify associated MediaController(s). 219a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.release(); 220a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 221a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 222a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 223a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { 224a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName + "; clientUid=" 225a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + clientUid + " ; rootHints=" + rootHints); 226a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // To ensure you are not allowing any arbitrary app to browse your app's contents, you 227a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // need to check the origin: 228a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!mPackageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { 229a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // If the request comes from an untrusted package, return null. No further calls will 230a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // be made to other media browsing methods. 231a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.w(TAG, "OnGetRoot: IGNORING request from untrusted package " + clientPackageName); 232a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return null; 233a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 234a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim //noinspection StatementWithEmptyBody 235a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (CarHelper.isValidCarPackage(clientPackageName)) { 236a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Optional: if your app needs to adapt ads, music library or anything else that 237a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // needs to run differently when connected to the car, this is where you should handle 238a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // it. 239a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 240a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return new BrowserRoot(MEDIA_ID_ROOT, null); 241a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 242a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 243a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 244a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { 2453ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon onLoadChildren(parentMediaId, result, null); 2463ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon } 2473ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 2483ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon @Override 2493ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result, 2503ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon final Bundle options) { 251a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!mMusicProvider.isInitialized()) { 252a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Use result.detach to allow calling result.sendResult from another thread: 253a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim result.detach(); 254a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 255a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() { 256a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 257a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onMusicCatalogReady(boolean success) { 258a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (success) { 2593ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon loadChildrenImpl(parentMediaId, result, options); 260a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 261a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(getString(R.string.error_no_metadata)); 262a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim result.sendResult(Collections.<MediaItem>emptyList()); 263a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 264a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 265a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim }); 266a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 267a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // If our music catalog is already loaded/cached, load them into result immediately 2683ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon loadChildrenImpl(parentMediaId, result, options); 269a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 270a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 271a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 272a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 273a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Actual implementation of onLoadChildren that assumes that MusicProvider is already 274a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * initialized. 275a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 276a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void loadChildrenImpl(final String parentMediaId, 2773ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon final Result<List<MediaItem>> result, final Bundle options) { 2783ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId + ", options=" + options); 2793ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 2803ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon int page = -1; 2813ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon int pageSize = -1; 2823ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 2833ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon if (options != null && (options.containsKey(MediaBrowserCompat.EXTRA_PAGE) 2843ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon || options.containsKey(MediaBrowserCompat.EXTRA_PAGE_SIZE))) { 2853ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1); 2863ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1); 2873ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 2883ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon if (page < 0 || pageSize < 1) { 289be6b908c8f7e61f1edf703d58cb59f0c6e6727c6Hyundo Moon result.sendResult(new ArrayList<MediaItem>()); 2903ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon return; 2913ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon } 2923ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon } 2933ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 2943ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon int fromIndex = page == -1 ? 0 : page * pageSize; 2953ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon int toIndex = 0; 296a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 297a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim List<MediaItem> mediaItems = new ArrayList<>(); 298a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 299a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (MEDIA_ID_ROOT.equals(parentMediaId)) { 300a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnLoadChildren.ROOT"); 3013ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon if (page <= 0) { 3023ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon mediaItems.add(new MediaItem( 3033ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon new MediaDescriptionCompat.Builder() 3043ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon .setMediaId(MEDIA_ID_MUSICS_BY_GENRE) 3053ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon .setTitle(getString(R.string.browse_genres)) 3063ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon .setIconUri(Uri.parse("android.resource://" + 3073ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon "com.example.android.supportv4.media/drawable/ic_by_genre")) 3083ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon .setSubtitle(getString(R.string.browse_genre_subtitle)) 3093ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon .build(), MediaItem.FLAG_BROWSABLE)); 3103ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon } 311a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 312a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) { 313a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnLoadChildren.GENRES"); 3143ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 3153ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon List<String> genres = mMusicProvider.getGenres(); 3163ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon toIndex = page == -1 ? genres.size() : Math.min(fromIndex + pageSize, genres.size()); 3173ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 3183ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon for (int i = fromIndex; i < toIndex; i++) { 3193ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon String genre = genres.get(i); 320a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaItem item = new MediaItem( 321a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim new MediaDescriptionCompat.Builder() 322a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE, 323a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim genre)) 324a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .setTitle(genre) 325a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .setSubtitle( 326a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim getString(R.string.browse_musics_by_genre_subtitle, genre)) 327a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .build(), MediaItem.FLAG_BROWSABLE 328a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim ); 329a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mediaItems.add(item); 330a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 331a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 332a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) { 333a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String genre = MediaIDHelper.getHierarchy(parentMediaId)[1]; 334a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnLoadChildren.SONGS_BY_GENRE genre=" + genre); 3353ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 3363ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon List<MediaMetadataCompat> tracks = mMusicProvider.getMusicsByGenre(genre); 3373ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon toIndex = page == -1 ? tracks.size() : Math.min(fromIndex + pageSize, tracks.size()); 3383ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 3393ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon for (int i = fromIndex; i < toIndex; i++) { 3403ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon MediaMetadataCompat track = tracks.get(i); 3413ee2ea199ba4802754fb9108a45ef7c646c0eebbHyundo Moon 342a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Since mediaMetadata fields are immutable, we need to create a copy, so we 343a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // can set a hierarchy-aware mediaID. We will need to know the media hierarchy 344a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // when we get a onPlayFromMusicID call, so we can create the proper queue based 345a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // on where the music was selected from (by artist, by genre, random, etc) 346a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String hierarchyAwareMediaID = MediaIDHelper.createMediaID( 347a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim track.getDescription().getMediaId(), MEDIA_ID_MUSICS_BY_GENRE, genre); 348a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaMetadataCompat trackCopy = new MediaMetadataCompat.Builder(track) 349a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID) 350a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .build(); 351a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaItem bItem = new MediaItem( 352a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim trackCopy.getDescription(), MediaItem.FLAG_PLAYABLE); 353a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mediaItems.add(bItem); 354a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 355a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 356a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.w(TAG, "Skipping unmatched parentMediaId: " + parentMediaId); 357a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 358a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnLoadChildren sending " + mediaItems.size() + " results for " 359a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + parentMediaId); 360a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim result.sendResult(mediaItems); 361a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 362a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 363a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private final class MediaSessionCallback extends MediaSessionCompat.Callback { 364a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 365a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onPlay() { 366a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "play"); 367a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 368a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue == null || mPlayingQueue.isEmpty()) { 369a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider); 370a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setQueue(mPlayingQueue); 371a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setQueueTitle(getString(R.string.random_queue_title)); 372a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // start playing from the beginning of the queue 373a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = 0; 374a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 375a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 376a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { 377a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 378a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 379a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 380a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 381a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 382a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onSkipToQueueItem(long queueId) { 383a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "OnSkipToQueueItem:" + queueId); 384a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 385a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { 386a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // set the current index on queue from the music Id: 387a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId); 388a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // play the music 389a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 390a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 391a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 392a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 393a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 394a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onSeekTo(long position) { 395a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "onSeekTo:" + position); 396a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.seekTo((int) position); 397a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 398a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 399a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 400a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onPlayFromMediaId(String mediaId, Bundle extras) { 401a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "playFromMediaId mediaId:" + mediaId + " extras=" + extras); 402a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 403a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // The mediaId used here is not the unique musicId. This one comes from the 404a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of 405a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary 406a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // so we can build the correct playing queue, based on where the track was 407a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // selected from. 408a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider); 409a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setQueue(mPlayingQueue); 410a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String queueTitle = getString(R.string.browse_musics_by_genre_subtitle, 411a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId)); 412a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setQueueTitle(queueTitle); 413a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 414a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { 415a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // set the current index on queue from the media Id: 416a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, mediaId); 417a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 418a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mCurrentIndexOnQueue < 0) { 419a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "playFromMediaId: media ID " + mediaId 420a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " could not be found on queue. Ignoring."); 421a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 422a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // play the music 423a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 424a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 425a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 426a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 427a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 428a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 429a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onPause() { 430a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "pause. current state=" + mPlayback.getState()); 431a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePauseRequest(); 432a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 433a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 434a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 435a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onStop() { 436a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "stop. current state=" + mPlayback.getState()); 437a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest(null); 438a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 439a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 440a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 441a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onSkipToNext() { 442a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "skipToNext"); 443a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue++; 444a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) { 445a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // This sample's behavior: skipping to next when in last song returns to the 446a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // first song. 447a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = 0; 448a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 449a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 450a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 451a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 452a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "skipToNext: cannot skip to next. next Index=" + 453a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue + " queue length=" + 454a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim (mPlayingQueue == null ? "null" : mPlayingQueue.size())); 455a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest("Cannot skip"); 456a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 457a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 458a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 459a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 460a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onSkipToPrevious() { 461a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "skipToPrevious"); 462a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue--; 463a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) { 464a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // This sample's behavior: skipping to previous when in first song restarts the 465a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // first song. 466a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = 0; 467a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 468a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 469a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 470a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 471a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "skipToPrevious: cannot skip to previous. previous Index=" + 472a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue + " queue length=" + 473a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim (mPlayingQueue == null ? "null" : mPlayingQueue.size())); 474a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest("Cannot skip"); 475a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 476a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 477a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 478a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 479a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onCustomAction(String action, Bundle extras) { 480a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (CUSTOM_ACTION_THUMBS_UP.equals(action)) { 481a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.i(TAG, "onCustomAction: favorite for current track"); 482a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaMetadataCompat track = getCurrentPlayingMusic(); 483a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (track != null) { 484a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String musicId = track.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID); 485a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMusicProvider.setFavorite(musicId, !mMusicProvider.isFavorite(musicId)); 486a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 487a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // playback state needs to be updated because the "Favorite" icon on the 488a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // custom action will change to reflect the new favorite state. 489a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(null); 490a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 491a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "Unsupported action: " + action); 492a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 493a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 494a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 495a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 496a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onPlayFromSearch(String query, Bundle extras) { 497a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "playFromSearch query=" + query); 498a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 499a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (TextUtils.isEmpty(query)) { 500a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // A generic search like "Play music" sends an empty query 501a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // and it's expected that we start playing something. What will be played depends 502a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // on the app: favorite playlist, "I'm feeling lucky", most recent, etc. 503a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider); 504a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 505a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider); 506a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 507a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 508a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "playFromSearch playqueue.length=" + mPlayingQueue.size()); 509a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setQueue(mPlayingQueue); 510a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 511a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { 512a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // immediately start playing from the beginning of the search results 513a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = 0; 514a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 515a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 516a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 517a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // if nothing was found, we need to warn the user and stop playing 518a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest(getString(R.string.no_search_results)); 519a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 520a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 521a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 522a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 523a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 524a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Handle a request to play music 525a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 526a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void handlePlayRequest() { 527a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState()); 528a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 529a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.removeCallbacksAndMessages(null); 530a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!mServiceStarted) { 531a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.v(TAG, "Starting service"); 532a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // The MusicService needs to keep running even after the calling MediaBrowser 533a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer 534a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // need to play media. 535a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim startService(new Intent(getApplicationContext(), MediaBrowserServiceSupport.class)); 536a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mServiceStarted = true; 537a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 538a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 539a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!mSession.isActive()) { 540a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setActive(true); 541a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 542a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 543a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 544a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updateMetadata(); 545a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue)); 546a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 547a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 548a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 549a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 550a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Handle a request to pause music 551a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 552a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void handlePauseRequest() { 553a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState()); 554a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.pause(); 555a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // reset the delayed stop handler. 556a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.removeCallbacksAndMessages(null); 557a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); 558a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 559a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 560a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 561a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Handle a request to stop music 562a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 563a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void handleStopRequest(String withError) { 564a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "handleStopRequest: mState=" + mPlayback.getState() + " error=" + withError); 565a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mPlayback.stop(true); 566a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // reset the delayed stop handler. 567a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.removeCallbacksAndMessages(null); 568a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); 569a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 570a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(withError); 571a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 572a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // service is no longer necessary. Will be started again if needed. 573a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim stopSelf(); 574a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mServiceStarted = false; 575a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 576a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 577a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void updateMetadata() { 578a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 579a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "Can't retrieve current metadata."); 580a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(getResources().getString(R.string.error_no_metadata)); 581a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return; 582a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 583a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaSessionCompat.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue); 584a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String musicId = MediaIDHelper.extractMusicIDFromMediaID( 585a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim queueItem.getDescription().getMediaId()); 586a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaMetadataCompat track = mMusicProvider.getMusic(musicId); 587a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim final String trackId = track.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID); 588a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (!musicId.equals(trackId)) { 589a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim IllegalStateException e = new IllegalStateException("track ID should match musicId."); 590a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.e(TAG, "track ID should match musicId. musicId=" + musicId + " trackId=" + trackId 591a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " mediaId from queueItem=" + queueItem.getDescription().getMediaId() 592a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " title from queueItem=" + queueItem.getDescription().getTitle() 593a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " mediaId from track=" + track.getDescription().getMediaId() 594a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " title from track=" + track.getDescription().getTitle() 595a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + " source.hashcode from track=" + track.getString( 596a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MusicProvider.CUSTOM_METADATA_TRACK_SOURCE).hashCode(), e); 597a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim throw e; 598a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 599a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "Updating metadata for MusicID= " + musicId); 600a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setMetadata(track); 601a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 602a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Set the proper album artwork on the media session, so it can be shown in the 603a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // locked screen and in other places. 604a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (track.getDescription().getIconBitmap() == null && 605a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim track.getDescription().getIconUri() != null) { 606a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String albumUri = track.getDescription().getIconUri().toString(); 607a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim AlbumArtCache.getInstance().fetch(albumUri, new AlbumArtCache.FetchListener() { 608a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 609a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) { 610a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaSessionCompat.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue); 611a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaMetadataCompat track = mMusicProvider.getMusic(trackId); 612a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim track = new MediaMetadataCompat.Builder(track) 613a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is used, 614a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // for example, on the lockscreen background when the media session is 615a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // active. 616a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap) 617a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // set small version of the album art in the DISPLAY_ICON. This is used 618a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // on the MediaDescription and thus it should be small to be serialized 619a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // if necessary. 620a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, icon) 621a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .build(); 622a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 623a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMusicProvider.updateMusic(trackId, track); 624a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 625a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // If we are still playing the same music 626a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String currentPlayingId = MediaIDHelper.extractMusicIDFromMediaID( 627a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim queueItem.getDescription().getMediaId()); 628a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (trackId.equals(currentPlayingId)) { 629a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setMetadata(track); 630a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 631a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 632a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim }); 633a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 634a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 635a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 636a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 637a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Update the current media player state, optionally showing an error message. 638a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * 639a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * @param error if not null, error message to present to the user. 640a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 641a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void updatePlaybackState(String error) { 642a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState()); 643a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim long position = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN; 644a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayback != null && mPlayback.isConnected()) { 645a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim position = mPlayback.getCurrentStreamPosition(); 646a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 647a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 648a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder() 649a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .setActions(getAvailableActions()); 650a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 651a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim setCustomAction(stateBuilder); 652a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim int state = mPlayback.getState(); 653a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 654a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // If there is an error message, send it to the playback state: 655a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (error != null) { 656a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Error states are really only supposed to be used for errors that cause playback to 657a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // stop unexpectedly and persist until the user takes action to fix it. 658a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim stateBuilder.setErrorMessage(error); 659a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim state = PlaybackStateCompat.STATE_ERROR; 660a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 661a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime()); 662a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 663a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Set the activeQueueItemId if the current index is valid. 664a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 665a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaSessionCompat.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue); 666a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim stateBuilder.setActiveQueueItemId(item.getQueueId()); 667a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 668a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 669a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mSession.setPlaybackState(stateBuilder.build()); 670a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 671a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (state == PlaybackStateCompat.STATE_PLAYING 672a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim || state == PlaybackStateCompat.STATE_PAUSED) { 673a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mMediaNotificationManager.startNotification(); 674a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 675a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 676a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 677a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { 678a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaMetadataCompat currentMusic = getCurrentPlayingMusic(); 679a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (currentMusic != null) { 680a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // Set appropriate "Favorite" icon on Custom action: 681a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim String musicId = currentMusic.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID); 682a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim int favoriteIcon = R.drawable.ic_star_off; 683a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mMusicProvider.isFavorite(musicId)) { 684a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim favoriteIcon = R.drawable.ic_star_on; 685a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 686a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "updatePlaybackState, setting Favorite custom action of music " 687a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + musicId + " current favorite=" + mMusicProvider.isFavorite(musicId)); 688a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim stateBuilder.addCustomAction(CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite), 689a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim favoriteIcon); 690a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 691a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 692a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 693a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private long getAvailableActions() { 694a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim long actions = PlaybackStateCompat.ACTION_PLAY 695a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID 696a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH; 697a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue == null || mPlayingQueue.isEmpty()) { 698a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return actions; 699a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 700a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayback.isPlaying()) { 701a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim actions |= PlaybackStateCompat.ACTION_PAUSE; 702a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 703a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mCurrentIndexOnQueue > 0) { 704a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; 705a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 706a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) { 707a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; 708a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 709a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return actions; 710a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 711a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 712a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private MediaMetadataCompat getCurrentPlayingMusic() { 713a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { 714a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaSessionCompat.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue); 715a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (item != null) { 716a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "getCurrentPlayingMusic for musicId=" 717a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim + item.getDescription().getMediaId()); 718a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return mMusicProvider.getMusic( 719a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaIDHelper 720a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim .extractMusicIDFromMediaID(item.getDescription().getMediaId())); 721a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 722a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 723a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return null; 724a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 725a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 726a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 727a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Implementation of the Playback.Callback interface 728a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 729a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 730a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onCompletion() { 731a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // The media player finished playing the current song, so we go ahead 732a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // and start the next. 733a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { 734a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // In this sample, we restart the playing queue when it gets to the end: 735a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue++; 736a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (mCurrentIndexOnQueue >= mPlayingQueue.size()) { 737a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mCurrentIndexOnQueue = 0; 738a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 739a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handlePlayRequest(); 740a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } else { 741a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim // If there is nothing to play, we stop and release the resources: 742a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim handleStopRequest(null); 743a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 744a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 745a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 746a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 747a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onPlaybackStatusChanged(int state) { 748a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(null); 749a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 750a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 751a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 752a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void onError(String error) { 753a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim updatePlaybackState(error); 754a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 755a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 756a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim /** 757a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * A simple handler that stops the service if playback is not active (playing) 758a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */ 759a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private static class DelayedStopHandler extends Handler { 760a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private final WeakReference<MediaBrowserServiceSupport> mWeakReference; 761a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 762a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim private DelayedStopHandler(MediaBrowserServiceSupport service) { 763a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim mWeakReference = new WeakReference<>(service); 764a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 765a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim 766a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim @Override 767a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim public void handleMessage(Message msg) { 768a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim MediaBrowserServiceSupport service = mWeakReference.get(); 769a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (service != null && service.mPlayback != null) { 770a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim if (service.mPlayback.isPlaying()) { 771a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "Ignoring delayed stop since the media player is in use."); 772a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim return; 773a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 774a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim Log.d(TAG, "Stopping service with delay handler."); 775a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim service.stopSelf(); 776a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim service.mServiceStarted = false; 777a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 778a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 779a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim } 780a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim} 781