1 /*
2  * Copyright (C) 2011 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.musicplayer;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapFactory;
28 import android.media.AudioManager;
29 import android.media.MediaMetadataRetriever;
30 import android.media.MediaPlayer;
31 import android.media.MediaPlayer.OnCompletionListener;
32 import android.media.MediaPlayer.OnErrorListener;
33 import android.media.MediaPlayer.OnPreparedListener;
34 import android.media.RemoteControlClient;
35 import android.net.Uri;
36 import android.net.wifi.WifiManager;
37 import android.net.wifi.WifiManager.WifiLock;
38 import android.os.IBinder;
39 import android.os.PowerManager;
40 import android.util.Log;
41 import android.widget.Toast;
42 
43 import java.io.IOException;
44 
45 /**
46  * Service that handles media playback. This is the Service through which we perform all the media
47  * handling in our application. Upon initialization, it starts a {@link MusicRetriever} to scan
48  * the user's media. Then, it waits for Intents (which come from our main activity,
49  * {@link MainActivity}, which signal the service to perform specific operations: Play, Pause,
50  * Rewind, Skip, etc.
51  */
52 public class MusicService extends Service implements OnCompletionListener, OnPreparedListener,
53                 OnErrorListener, MusicFocusable,
54                 PrepareMusicRetrieverTask.MusicRetrieverPreparedListener {
55 
56     // The tag we put on debug messages
57     final static String TAG = "RandomMusicPlayer";
58 
59     // These are the Intent actions that we are prepared to handle. Notice that the fact these
60     // constants exist in our class is a mere convenience: what really defines the actions our
61     // service can handle are the <action> tags in the <intent-filters> tag for our service in
62     // AndroidManifest.xml.
63     public static final String ACTION_TOGGLE_PLAYBACK =
64             "com.example.android.musicplayer.action.TOGGLE_PLAYBACK";
65     public static final String ACTION_PLAY = "com.example.android.musicplayer.action.PLAY";
66     public static final String ACTION_PAUSE = "com.example.android.musicplayer.action.PAUSE";
67     public static final String ACTION_STOP = "com.example.android.musicplayer.action.STOP";
68     public static final String ACTION_SKIP = "com.example.android.musicplayer.action.SKIP";
69     public static final String ACTION_REWIND = "com.example.android.musicplayer.action.REWIND";
70     public static final String ACTION_URL = "com.example.android.musicplayer.action.URL";
71 
72     // The volume we set the media player to when we lose audio focus, but are allowed to reduce
73     // the volume instead of stopping playback.
74     public static final float DUCK_VOLUME = 0.1f;
75 
76     // our media player
77     MediaPlayer mPlayer = null;
78 
79     // our AudioFocusHelper object, if it's available (it's available on SDK level >= 8)
80     // If not available, this will be null. Always check for null before using!
81     AudioFocusHelper mAudioFocusHelper = null;
82 
83     // indicates the state our service:
84     enum State {
85         Retrieving, // the MediaRetriever is retrieving music
86         Stopped,    // media player is stopped and not prepared to play
87         Preparing,  // media player is preparing...
88         Playing,    // playback active (media player ready!). (but the media player may actually be
89                     // paused in this state if we don't have audio focus. But we stay in this state
90                     // so that we know we have to resume playback once we get focus back)
91         Paused      // playback paused (media player ready!)
92     };
93 
94     State mState = State.Retrieving;
95 
96     // if in Retrieving mode, this flag indicates whether we should start playing immediately
97     // when we are ready or not.
98     boolean mStartPlayingAfterRetrieve = false;
99 
100     // if mStartPlayingAfterRetrieve is true, this variable indicates the URL that we should
101     // start playing when we are ready. If null, we should play a random song from the device
102     Uri mWhatToPlayAfterRetrieve = null;
103 
104     enum PauseReason {
105         UserRequest,  // paused by user request
106         FocusLoss,    // paused because of audio focus loss
107     };
108 
109     // why did we pause? (only relevant if mState == State.Paused)
110     PauseReason mPauseReason = PauseReason.UserRequest;
111 
112     // do we have audio focus?
113     enum AudioFocus {
114         NoFocusNoDuck,    // we don't have audio focus, and can't duck
115         NoFocusCanDuck,   // we don't have focus, but can play at a low volume ("ducking")
116         Focused           // we have full audio focus
117     }
118     AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
119 
120     // title of the song we are currently playing
121     String mSongTitle = "";
122 
123     // whether the song we are playing is streaming from the network
124     boolean mIsStreaming = false;
125 
126     // Wifi lock that we hold when streaming files from the internet, in order to prevent the
127     // device from shutting off the Wifi radio
128     WifiLock mWifiLock;
129 
130     // The ID we use for the notification (the onscreen alert that appears at the notification
131     // area at the top of the screen as an icon -- and as text as well if the user expands the
132     // notification area).
133     final int NOTIFICATION_ID = 1;
134 
135     // Our instance of our MusicRetriever, which handles scanning for media and
136     // providing titles and URIs as we need.
137     MusicRetriever mRetriever;
138 
139     // our RemoteControlClient object, which will use remote control APIs available in
140     // SDK level >= 14, if they're available.
141     RemoteControlClientCompat mRemoteControlClientCompat;
142 
143     // Dummy album art we will pass to the remote control (if the APIs are available).
144     Bitmap mDummyAlbumArt;
145 
146     // The component name of MusicIntentReceiver, for use with media button and remote control
147     // APIs
148     ComponentName mMediaButtonReceiverComponent;
149 
150     AudioManager mAudioManager;
151     NotificationManager mNotificationManager;
152 
153     Notification.Builder mNotificationBuilder = null;
154 
155     /**
156      * Makes sure the media player exists and has been reset. This will create the media player
157      * if needed, or reset the existing media player if one already exists.
158      */
createMediaPlayerIfNeeded()159     void createMediaPlayerIfNeeded() {
160         if (mPlayer == null) {
161             mPlayer = new MediaPlayer();
162 
163             // Make sure the media player will acquire a wake-lock while playing. If we don't do
164             // that, the CPU might go to sleep while the song is playing, causing playback to stop.
165             //
166             // Remember that to use this, we have to declare the android.permission.WAKE_LOCK
167             // permission in AndroidManifest.xml.
168             mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
169 
170             // we want the media player to notify us when it's ready preparing, and when it's done
171             // playing:
172             mPlayer.setOnPreparedListener(this);
173             mPlayer.setOnCompletionListener(this);
174             mPlayer.setOnErrorListener(this);
175         }
176         else
177             mPlayer.reset();
178     }
179 
180     @Override
onCreate()181     public void onCreate() {
182         Log.i(TAG, "debug: Creating service");
183 
184         // Create the Wifi lock (this does not acquire the lock, this just creates it)
185         mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
186                         .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
187 
188         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
189         mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
190 
191         // Create the retriever and start an asynchronous task that will prepare it.
192         mRetriever = new MusicRetriever(getContentResolver());
193         (new PrepareMusicRetrieverTask(mRetriever,this)).execute();
194 
195         // create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above)
196         if (android.os.Build.VERSION.SDK_INT >= 8)
197             mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
198         else
199             mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus
200 
201         mDummyAlbumArt = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_album_art);
202 
203         mMediaButtonReceiverComponent = new ComponentName(this, MusicIntentReceiver.class);
204     }
205 
206     /**
207      * Called when we receive an Intent. When we receive an intent sent to us via startService(),
208      * this is the method that gets called. So here we react appropriately depending on the
209      * Intent's action, which specifies what is being requested of us.
210      */
211     @Override
onStartCommand(Intent intent, int flags, int startId)212     public int onStartCommand(Intent intent, int flags, int startId) {
213         String action = intent.getAction();
214         if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest();
215         else if (action.equals(ACTION_PLAY)) processPlayRequest();
216         else if (action.equals(ACTION_PAUSE)) processPauseRequest();
217         else if (action.equals(ACTION_SKIP)) processSkipRequest();
218         else if (action.equals(ACTION_STOP)) processStopRequest();
219         else if (action.equals(ACTION_REWIND)) processRewindRequest();
220         else if (action.equals(ACTION_URL)) processAddRequest(intent);
221 
222         return START_NOT_STICKY; // Means we started the service, but don't want it to
223                                  // restart in case it's killed.
224     }
225 
processTogglePlaybackRequest()226     void processTogglePlaybackRequest() {
227         if (mState == State.Paused || mState == State.Stopped) {
228             processPlayRequest();
229         } else {
230             processPauseRequest();
231         }
232     }
233 
processPlayRequest()234     void processPlayRequest() {
235         if (mState == State.Retrieving) {
236             // If we are still retrieving media, just set the flag to start playing when we're
237             // ready
238             mWhatToPlayAfterRetrieve = null; // play a random song
239             mStartPlayingAfterRetrieve = true;
240             return;
241         }
242 
243         tryToGetAudioFocus();
244 
245         // actually play the song
246 
247         if (mState == State.Stopped) {
248             // If we're stopped, just go ahead to the next song and start playing
249             playNextSong(null);
250         }
251         else if (mState == State.Paused) {
252             // If we're paused, just continue playback and restore the 'foreground service' state.
253             mState = State.Playing;
254             setUpAsForeground(mSongTitle + " (playing)");
255             configAndStartMediaPlayer();
256         }
257 
258         // Tell any remote controls that our playback state is 'playing'.
259         if (mRemoteControlClientCompat != null) {
260             mRemoteControlClientCompat
261                     .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
262         }
263     }
264 
processPauseRequest()265     void processPauseRequest() {
266         if (mState == State.Retrieving) {
267             // If we are still retrieving media, clear the flag that indicates we should start
268             // playing when we're ready
269             mStartPlayingAfterRetrieve = false;
270             return;
271         }
272 
273         if (mState == State.Playing) {
274             // Pause media player and cancel the 'foreground service' state.
275             mState = State.Paused;
276             mPlayer.pause();
277             relaxResources(false); // while paused, we always retain the MediaPlayer
278             // do not give up audio focus
279         }
280 
281         // Tell any remote controls that our playback state is 'paused'.
282         if (mRemoteControlClientCompat != null) {
283             mRemoteControlClientCompat
284                     .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
285         }
286     }
287 
processRewindRequest()288     void processRewindRequest() {
289         if (mState == State.Playing || mState == State.Paused)
290             mPlayer.seekTo(0);
291     }
292 
processSkipRequest()293     void processSkipRequest() {
294         if (mState == State.Playing || mState == State.Paused) {
295             tryToGetAudioFocus();
296             playNextSong(null);
297         }
298     }
299 
processStopRequest()300     void processStopRequest() {
301         processStopRequest(false);
302     }
303 
processStopRequest(boolean force)304     void processStopRequest(boolean force) {
305         if (mState == State.Playing || mState == State.Paused || force) {
306             mState = State.Stopped;
307 
308             // let go of all resources...
309             relaxResources(true);
310             giveUpAudioFocus();
311 
312             // Tell any remote controls that our playback state is 'paused'.
313             if (mRemoteControlClientCompat != null) {
314                 mRemoteControlClientCompat
315                         .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
316             }
317 
318             // service is no longer necessary. Will be started again if needed.
319             stopSelf();
320         }
321     }
322 
323     /**
324      * Releases resources used by the service for playback. This includes the "foreground service"
325      * status and notification, the wake locks and possibly the MediaPlayer.
326      *
327      * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
328      */
relaxResources(boolean releaseMediaPlayer)329     void relaxResources(boolean releaseMediaPlayer) {
330         // stop being a foreground service
331         stopForeground(true);
332 
333         // stop and release the Media Player, if it's available
334         if (releaseMediaPlayer && mPlayer != null) {
335             mPlayer.reset();
336             mPlayer.release();
337             mPlayer = null;
338         }
339 
340         // we can also release the Wifi lock, if we're holding it
341         if (mWifiLock.isHeld()) mWifiLock.release();
342     }
343 
giveUpAudioFocus()344     void giveUpAudioFocus() {
345         if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null
346                                 && mAudioFocusHelper.abandonFocus())
347             mAudioFocus = AudioFocus.NoFocusNoDuck;
348     }
349 
350     /**
351      * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This
352      * method starts/restarts the MediaPlayer respecting the current audio focus state. So if
353      * we have focus, it will play normally; if we don't have focus, it will either leave the
354      * MediaPlayer paused or set it to a low volume, depending on what is allowed by the
355      * current focus settings. This method assumes mPlayer != null, so if you are calling it,
356      * you have to do so from a context where you are sure this is the case.
357      */
configAndStartMediaPlayer()358     void configAndStartMediaPlayer() {
359         if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
360             // If we don't have audio focus and can't duck, we have to pause, even if mState
361             // is State.Playing. But we stay in the Playing state so that we know we have to resume
362             // playback once we get the focus back.
363             if (mPlayer.isPlaying()) mPlayer.pause();
364             return;
365         }
366         else if (mAudioFocus == AudioFocus.NoFocusCanDuck)
367             mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);  // we'll be relatively quiet
368         else
369             mPlayer.setVolume(1.0f, 1.0f); // we can be loud
370 
371         if (!mPlayer.isPlaying()) mPlayer.start();
372     }
373 
processAddRequest(Intent intent)374     void processAddRequest(Intent intent) {
375         // user wants to play a song directly by URL or path. The URL or path comes in the "data"
376         // part of the Intent. This Intent is sent by {@link MainActivity} after the user
377         // specifies the URL/path via an alert box.
378         if (mState == State.Retrieving) {
379             // we'll play the requested URL right after we finish retrieving
380             mWhatToPlayAfterRetrieve = intent.getData();
381             mStartPlayingAfterRetrieve = true;
382         }
383         else if (mState == State.Playing || mState == State.Paused || mState == State.Stopped) {
384             Log.i(TAG, "Playing from URL/path: " + intent.getData().toString());
385             tryToGetAudioFocus();
386             playNextSong(intent.getData().toString());
387         }
388     }
389 
tryToGetAudioFocus()390     void tryToGetAudioFocus() {
391         if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
392                         && mAudioFocusHelper.requestFocus())
393             mAudioFocus = AudioFocus.Focused;
394     }
395 
396     /**
397      * Starts playing the next song. If manualUrl is null, the next song will be randomly selected
398      * from our Media Retriever (that is, it will be a random song in the user's device). If
399      * manualUrl is non-null, then it specifies the URL or path to the song that will be played
400      * next.
401      */
playNextSong(String manualUrl)402     void playNextSong(String manualUrl) {
403         mState = State.Stopped;
404         relaxResources(false); // release everything except MediaPlayer
405 
406         try {
407             MusicRetriever.Item playingItem = null;
408             if (manualUrl != null) {
409                 // set the source of the media player to a manual URL or path
410                 createMediaPlayerIfNeeded();
411                 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
412                 mPlayer.setDataSource(manualUrl);
413                 mIsStreaming = manualUrl.startsWith("http:") || manualUrl.startsWith("https:");
414 
415                 playingItem = new MusicRetriever.Item(0, null, manualUrl, null, 0);
416             }
417             else {
418                 mIsStreaming = false; // playing a locally available song
419 
420                 playingItem = mRetriever.getRandomItem();
421                 if (playingItem == null) {
422                     Toast.makeText(this,
423                             "No available music to play. Place some music on your external storage "
424                             + "device (e.g. your SD card) and try again.",
425                             Toast.LENGTH_LONG).show();
426                     processStopRequest(true); // stop everything!
427                     return;
428                 }
429 
430                 // set the source of the media player a a content URI
431                 createMediaPlayerIfNeeded();
432                 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
433                 mPlayer.setDataSource(getApplicationContext(), playingItem.getURI());
434             }
435 
436             mSongTitle = playingItem.getTitle();
437 
438             mState = State.Preparing;
439             setUpAsForeground(mSongTitle + " (loading)");
440 
441             // Use the media button APIs (if available) to register ourselves for media button
442             // events
443 
444             MediaButtonHelper.registerMediaButtonEventReceiverCompat(
445                     mAudioManager, mMediaButtonReceiverComponent);
446 
447             // Use the remote control APIs (if available) to set the playback state
448 
449             if (mRemoteControlClientCompat == null) {
450                 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
451                 intent.setComponent(mMediaButtonReceiverComponent);
452                 mRemoteControlClientCompat = new RemoteControlClientCompat(
453                         PendingIntent.getBroadcast(this /*context*/,
454                                 0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/));
455                 RemoteControlHelper.registerRemoteControlClient(mAudioManager,
456                         mRemoteControlClientCompat);
457             }
458 
459             mRemoteControlClientCompat.setPlaybackState(
460                     RemoteControlClient.PLAYSTATE_PLAYING);
461 
462             mRemoteControlClientCompat.setTransportControlFlags(
463                     RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
464                     RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
465                     RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
466                     RemoteControlClient.FLAG_KEY_MEDIA_STOP);
467 
468             // Update the remote controls
469             mRemoteControlClientCompat.editMetadata(true)
470                     .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, playingItem.getArtist())
471                     .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, playingItem.getAlbum())
472                     .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, playingItem.getTitle())
473                     .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
474                             playingItem.getDuration())
475                     // TODO: fetch real item artwork
476                     .putBitmap(
477                             RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
478                             mDummyAlbumArt)
479                     .apply();
480 
481             // starts preparing the media player in the background. When it's done, it will call
482             // our OnPreparedListener (that is, the onPrepared() method on this class, since we set
483             // the listener to 'this').
484             //
485             // Until the media player is prepared, we *cannot* call start() on it!
486             mPlayer.prepareAsync();
487 
488             // If we are streaming from the internet, we want to hold a Wifi lock, which prevents
489             // the Wifi radio from going to sleep while the song is playing. If, on the other hand,
490             // we are *not* streaming, we want to release the lock if we were holding it before.
491             if (mIsStreaming) mWifiLock.acquire();
492             else if (mWifiLock.isHeld()) mWifiLock.release();
493         }
494         catch (IOException ex) {
495             Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
496             ex.printStackTrace();
497         }
498     }
499 
500     /** Called when media player is done playing current song. */
onCompletion(MediaPlayer player)501     public void onCompletion(MediaPlayer player) {
502         // The media player finished playing the current song, so we go ahead and start the next.
503         playNextSong(null);
504     }
505 
506     /** Called when media player is done preparing. */
onPrepared(MediaPlayer player)507     public void onPrepared(MediaPlayer player) {
508         // The media player is done preparing. That means we can start playing!
509         mState = State.Playing;
510         updateNotification(mSongTitle + " (playing)");
511         configAndStartMediaPlayer();
512     }
513 
514     /** Updates the notification. */
updateNotification(String text)515     void updateNotification(String text) {
516         PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
517                 new Intent(getApplicationContext(), MainActivity.class),
518                 PendingIntent.FLAG_UPDATE_CURRENT);
519         mNotificationBuilder.setContentText(text)
520                 .setContentIntent(pi);
521         mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
522     }
523 
524     /**
525      * Configures service as a foreground service. A foreground service is a service that's doing
526      * something the user is actively aware of (such as playing music), and must appear to the
527      * user as a notification. That's why we create the notification here.
528      */
setUpAsForeground(String text)529     void setUpAsForeground(String text) {
530         PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
531                 new Intent(getApplicationContext(), MainActivity.class),
532                 PendingIntent.FLAG_UPDATE_CURRENT);
533 
534         // Build the notification object.
535         mNotificationBuilder = new Notification.Builder(getApplicationContext())
536                 .setSmallIcon(R.drawable.ic_stat_playing)
537                 .setTicker(text)
538                 .setWhen(System.currentTimeMillis())
539                 .setContentTitle("RandomMusicPlayer")
540                 .setContentText(text)
541                 .setContentIntent(pi)
542                 .setOngoing(true);
543 
544         startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
545     }
546 
547     /**
548      * Called when there's an error playing media. When this happens, the media player goes to
549      * the Error state. We warn the user about the error and reset the media player.
550      */
onError(MediaPlayer mp, int what, int extra)551     public boolean onError(MediaPlayer mp, int what, int extra) {
552         Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
553             Toast.LENGTH_SHORT).show();
554         Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra));
555 
556         mState = State.Stopped;
557         relaxResources(true);
558         giveUpAudioFocus();
559         return true; // true indicates we handled the error
560     }
561 
onGainedAudioFocus()562     public void onGainedAudioFocus() {
563         Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
564         mAudioFocus = AudioFocus.Focused;
565 
566         // restart media player with new focus settings
567         if (mState == State.Playing)
568             configAndStartMediaPlayer();
569     }
570 
onLostAudioFocus(boolean canDuck)571     public void onLostAudioFocus(boolean canDuck) {
572         Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
573             "no duck"), Toast.LENGTH_SHORT).show();
574         mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
575 
576         // start/restart/pause media player with new focus settings
577         if (mPlayer != null && mPlayer.isPlaying())
578             configAndStartMediaPlayer();
579     }
580 
onMusicRetrieverPrepared()581     public void onMusicRetrieverPrepared() {
582         // Done retrieving!
583         mState = State.Stopped;
584 
585         // If the flag indicates we should start playing after retrieving, let's do that now.
586         if (mStartPlayingAfterRetrieve) {
587             tryToGetAudioFocus();
588             playNextSong(mWhatToPlayAfterRetrieve == null ?
589                     null : mWhatToPlayAfterRetrieve.toString());
590         }
591     }
592 
593 
594     @Override
onDestroy()595     public void onDestroy() {
596         // Service is being killed, so make sure we release our resources
597         mState = State.Stopped;
598         relaxResources(true);
599         giveUpAudioFocus();
600     }
601 
602     @Override
onBind(Intent arg0)603     public IBinder onBind(Intent arg0) {
604         return null;
605     }
606 }
607