OSDN Git Service

Fix merge derp.
[android-x86/packages-apps-Eleven.git] / src / com / andrew / apollo / MusicPlaybackService.java
1 /*
2  * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance with the
4  * License. You may obtain a copy of the License at
5  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6  * or agreed to in writing, software distributed under the License is
7  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8  * KIND, either express or implied. See the License for the specific language
9  * governing permissions and limitations under the License.
10  */
11
12 package com.andrew.apollo;
13
14 import android.annotation.SuppressLint;
15 import android.app.AlarmManager;
16 import android.app.PendingIntent;
17 import android.app.Service;
18 import android.appwidget.AppWidgetManager;
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.SharedPreferences;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.media.AudioManager;
29 import android.media.AudioManager.OnAudioFocusChangeListener;
30 import android.media.MediaMetadataRetriever;
31 import android.media.MediaPlayer;
32 import android.media.MediaPlayer.OnCompletionListener;
33 import android.media.RemoteControlClient;
34 import android.media.audiofx.AudioEffect;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.IBinder;
39 import android.os.Looper;
40 import android.os.Message;
41 import android.os.PowerManager;
42 import android.os.PowerManager.WakeLock;
43 import android.os.RemoteException;
44 import android.os.SystemClock;
45 import android.provider.MediaStore;
46 import android.provider.MediaStore.Audio.AudioColumns;
47
48 import com.andrew.apollo.appwidgets.AppWidgetLarge;
49 import com.andrew.apollo.appwidgets.AppWidgetLargeAlternate;
50 import com.andrew.apollo.appwidgets.AppWidgetSmall;
51 import com.andrew.apollo.appwidgets.RecentWidgetProvider;
52 import com.andrew.apollo.cache.ImageCache;
53 import com.andrew.apollo.cache.ImageFetcher;
54 import com.andrew.apollo.provider.FavoritesStore;
55 import com.andrew.apollo.provider.RecentStore;
56 import com.andrew.apollo.utils.ApolloUtils;
57 import com.andrew.apollo.utils.Lists;
58 import com.andrew.apollo.utils.MusicUtils;
59 import com.andrew.apollo.utils.PreferenceUtils;
60
61 import java.io.IOException;
62 import java.lang.ref.WeakReference;
63 import java.util.LinkedList;
64 import java.util.Random;
65 import java.util.TreeSet;
66
67 /**
68  * A backbround {@link Service} used to keep music playing between activities
69  * and when the user moves Apollo into the background.
70  */
71 @SuppressLint("NewApi")
72 public class MusicPlaybackService extends Service {
73     /**
74      * Indicates that the music has paused or resumed
75      */
76     public static final String PLAYSTATE_CHANGED = "com.andrew.apollo.playstatechanged";
77
78     /**
79      * Indicates the meta data has changed in some way, like a track change
80      */
81     public static final String META_CHANGED = "com.andrew.apollo.metachanged";
82
83     /**
84      * Indicates the queue has been updated
85      */
86     public static final String QUEUE_CHANGED = "com.andrew.apollo.queuechanged";
87
88     /**
89      * Indicates the repeat mode chaned
90      */
91     public static final String REPEATMODE_CHANGED = "com.andrew.apollo.repeatmodechanged";
92
93     /**
94      * Indicates the shuffle mode chaned
95      */
96     public static final String SHUFFLEMODE_CHANGED = "com.andrew.apollo.shufflemodechanged";
97
98     /**
99      * For backwards compatibility reasons, also provide sticky
100      * broadcasts under the music package
101      */
102     public static final String APOLLO_PACKAGE_NAME = "com.andrew.apollo";
103     public static final String MUSIC_PACKAGE_NAME = "com.android.music";
104
105     /**
106      * Called to indicate a general service commmand. Used in
107      * {@link MediaButtonIntentReceiver}
108      */
109     public static final String SERVICECMD = "com.andrew.apollo.musicservicecommand";
110
111     /**
112      * Called to go toggle between pausing and playing the music
113      */
114     public static final String TOGGLEPAUSE_ACTION = "com.andrew.apollo.togglepause";
115
116     /**
117      * Called to go to pause the playback
118      */
119     public static final String PAUSE_ACTION = "com.andrew.apollo.pause";
120
121     /**
122      * Called to go to stop the playback
123      */
124     public static final String STOP_ACTION = "com.andrew.apollo.stop";
125
126     /**
127      * Called to go to the previous track
128      */
129     public static final String PREVIOUS_ACTION = "com.andrew.apollo.previous";
130
131     /**
132      * Called to go to the next track
133      */
134     public static final String NEXT_ACTION = "com.andrew.apollo.next";
135
136     /**
137      * Called to change the repeat mode
138      */
139     public static final String REPEAT_ACTION = "com.andrew.apollo.repeat";
140
141     /**
142      * Called to change the shuffle mode
143      */
144     public static final String SHUFFLE_ACTION = "com.andrew.apollo.shuffle";
145
146     /**
147      * Called to update the service about the foreground state of Apollo's activities
148      */
149     public static final String FOREGROUND_STATE_CHANGED = "com.andrew.apollo.fgstatechanged";
150
151     public static final String NOW_IN_FOREGROUND = "nowinforeground";
152
153     /**
154      * Used to easily notify a list that it should refresh. i.e. A playlist
155      * changes
156      */
157     public static final String REFRESH = "com.andrew.apollo.refresh";
158
159     /**
160      * Used by the alarm intent to remove the notification
161      */
162     private static final String KILL_NOTIFICATION = "com.andrew.apollo.killnotification";
163
164     /**
165      * Called to update the remote control client
166      */
167     public static final String UPDATE_LOCKSCREEN = "com.andrew.apollo.updatelockscreen";
168
169     public static final String CMDNAME = "command";
170
171     public static final String CMDTOGGLEPAUSE = "togglepause";
172
173     public static final String CMDSTOP = "stop";
174
175     public static final String CMDPAUSE = "pause";
176
177     public static final String CMDPLAY = "play";
178
179     public static final String CMDPREVIOUS = "previous";
180
181     public static final String CMDNEXT = "next";
182
183     public static final String CMDNOTIF = "buttonId";
184
185     private static final int IDCOLIDX = 0;
186
187     /**
188      * Moves a list to the front of the queue
189      */
190     public static final int NOW = 1;
191
192     /**
193      * Moves a list to the next position in the queue
194      */
195     public static final int NEXT = 2;
196
197     /**
198      * Moves a list to the last position in the queue
199      */
200     public static final int LAST = 3;
201
202     /**
203      * Shuffles no songs, turns shuffling off
204      */
205     public static final int SHUFFLE_NONE = 0;
206
207     /**
208      * Shuffles all songs
209      */
210     public static final int SHUFFLE_NORMAL = 1;
211
212     /**
213      * Party shuffle
214      */
215     public static final int SHUFFLE_AUTO = 2;
216
217     /**
218      * Turns repeat off
219      */
220     public static final int REPEAT_NONE = 0;
221
222     /**
223      * Repeats the current track in a list
224      */
225     public static final int REPEAT_CURRENT = 1;
226
227     /**
228      * Repeats all the tracks in a list
229      */
230     public static final int REPEAT_ALL = 2;
231
232     /**
233      * Indicates when the track ends
234      */
235     private static final int TRACK_ENDED = 1;
236
237     /**
238      * Indicates that the current track was changed the next track
239      */
240     private static final int TRACK_WENT_TO_NEXT = 2;
241
242     /**
243      * Indicates when the release the wake lock
244      */
245     private static final int RELEASE_WAKELOCK = 3;
246
247     /**
248      * Indicates the player died
249      */
250     private static final int SERVER_DIED = 4;
251
252     /**
253      * Indicates some sort of focus change, maybe a phone call
254      */
255     private static final int FOCUSCHANGE = 5;
256
257     /**
258      * Indicates to fade the volume down
259      */
260     private static final int FADEDOWN = 6;
261
262     /**
263      * Indicates to fade the volume back up
264      */
265     private static final int FADEUP = 7;
266
267     /**
268      * Idle time before stopping the foreground notfication (1 minute)
269      */
270     private static final int IDLE_DELAY = 60000;
271
272     /**
273      * The max size allowed for the track history
274      */
275     private static final int MAX_HISTORY_SIZE = 100;
276
277     /**
278      * The columns used to retrieve any info from the current track
279      */
280     private static final String[] PROJECTION = new String[] {
281             "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
282             MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
283             MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
284             MediaStore.Audio.Media.ARTIST_ID
285     };
286
287     /**
288      * Keeps a mapping of the track history
289      */
290     private static final LinkedList<Integer> mHistory = Lists.newLinkedList();
291
292     /**
293      * Used to shuffle the tracks
294      */
295     private static final Shuffler mShuffler = new Shuffler();
296
297     /**
298      * Used to save the queue as reverse hexadecimal numbers, which we can
299      * generate faster than normal decimal or hexadecimal numbers, which in
300      * turn allows us to save the playlist more often without worrying too
301      * much about performance
302      */
303     private static final char HEX_DIGITS[] = new char[] {
304         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
305     };
306
307     /**
308      * Service stub
309      */
310     private final IBinder mBinder = new ServiceStub(this);
311
312     /**
313      * 4x1 widget
314      */
315     private final AppWidgetSmall mAppWidgetSmall = AppWidgetSmall.getInstance();
316
317     /**
318      * 4x2 widget
319      */
320     private final AppWidgetLarge mAppWidgetLarge = AppWidgetLarge.getInstance();
321
322     /**
323      * 4x2 alternate widget
324      */
325     private final AppWidgetLargeAlternate mAppWidgetLargeAlternate = AppWidgetLargeAlternate
326             .getInstance();
327
328     /**
329      * Recently listened widget
330      */
331     private final RecentWidgetProvider mRecentWidgetProvider = RecentWidgetProvider.getInstance();
332
333     /**
334      * The media player
335      */
336     private MultiPlayer mPlayer;
337
338     /**
339      * The path of the current file to play
340      */
341     private String mFileToPlay;
342
343     /**
344      * Keeps the service running when the screen is off
345      */
346     private WakeLock mWakeLock;
347
348     /**
349      * Alarm intent for removing the notification when nothing is playing
350      * for some time
351      */
352     private AlarmManager mAlarmManager;
353     private PendingIntent mKillNotificationIntent;
354
355     /**
356      * The cursor used to retrieve info on the current track and run the
357      * necessary queries to play audio files
358      */
359     private Cursor mCursor;
360
361     /**
362      * Monitors the audio state
363      */
364     private AudioManager mAudioManager;
365
366     /**
367      * Settings used to save and retrieve the queue and history
368      */
369     private SharedPreferences mPreferences;
370
371     /**
372      * Used to know when the service is active
373      */
374     private boolean mServiceInUse = false;
375
376     /**
377      * Used to know if something should be playing or not
378      */
379     private boolean mIsSupposedToBePlaying = false;
380
381     /**
382      * Used to indicate if the queue can be saved
383      */
384     private boolean mQueueIsSaveable = true;
385
386     /**
387      * Used to track what type of audio focus loss caused the playback to pause
388      */
389     private boolean mPausedByTransientLossOfFocus = false;
390
391     /**
392      * Used to track whether any of Apollo's activities is in the foreground
393      */
394     private boolean mAnyActivityInForeground = false;
395
396     /**
397      * Lock screen controls
398      */
399     private RemoteControlClient mRemoteControlClient;
400
401     private ComponentName mMediaButtonReceiverComponent;
402
403     // We use this to distinguish between different cards when saving/restoring
404     // playlists
405     private int mCardId;
406
407     private int mPlayListLen = 0;
408
409     private int mPlayPos = -1;
410
411     private int mNextPlayPos = -1;
412
413     private int mOpenFailedCounter = 0;
414
415     private int mMediaMountedCount = 0;
416
417     private int mShuffleMode = SHUFFLE_NONE;
418
419     private int mRepeatMode = REPEAT_NONE;
420
421     private int mServiceStartId = -1;
422
423     private long[] mPlayList = null;
424
425     private long[] mAutoShuffleList = null;
426
427     private MusicPlayerHandler mPlayerHandler;
428
429     private DelayedHandler mDelayedStopHandler;
430
431     private BroadcastReceiver mUnmountReceiver = null;
432
433     /**
434      * Image cache
435      */
436     private ImageFetcher mImageFetcher;
437
438     /**
439      * Used to build the notification
440      */
441     private NotificationHelper mNotificationHelper;
442
443     /**
444      * Recently listened database
445      */
446     private RecentStore mRecentsCache;
447
448     /**
449      * Favorites database
450      */
451     private FavoritesStore mFavoritesCache;
452
453     /**
454      * {@inheritDoc}
455      */
456     @Override
457     public IBinder onBind(final Intent intent) {
458         mDelayedStopHandler.removeCallbacksAndMessages(null);
459         mServiceInUse = true;
460         return mBinder;
461     }
462
463     /**
464      * {@inheritDoc}
465      */
466     @Override
467     public boolean onUnbind(final Intent intent) {
468         mServiceInUse = false;
469         saveQueue(true);
470
471         if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
472             // Something is currently playing, or will be playing once
473             // an in-progress action requesting audio focus ends, so don't stop
474             // the service now.
475             return true;
476
477             // If there is a playlist but playback is paused, then wait a while
478             // before stopping the service, so that pause/resume isn't slow.
479             // Also delay stopping the service if we're transitioning between
480             // tracks.
481         } else if (mPlayListLen > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
482             final Message msg = mDelayedStopHandler.obtainMessage();
483             mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
484             return true;
485         }
486         stopSelf(mServiceStartId);
487         return true;
488     }
489
490     /**
491      * {@inheritDoc}
492      */
493     @Override
494     public void onRebind(final Intent intent) {
495         mDelayedStopHandler.removeCallbacksAndMessages(null);
496         mServiceInUse = true;
497     }
498
499     /**
500      * {@inheritDoc}
501      */
502     @Override
503     public void onCreate() {
504         super.onCreate();
505
506         // Initialize the favorites and recents databases
507         mRecentsCache = RecentStore.getInstance(this);
508         mFavoritesCache = FavoritesStore.getInstance(this);
509
510         // Initialize the notification helper
511         mNotificationHelper = new NotificationHelper(this);
512
513         // Initialize the image fetcher
514         mImageFetcher = ImageFetcher.getInstance(this);
515         // Initialize the image cache
516         mImageFetcher.setImageCache(ImageCache.getInstance(this));
517
518         // Start up the thread running the service. Note that we create a
519         // separate thread because the service normally runs in the process's
520         // main thread, which we don't want to block. We also make it
521         // background priority so CPU-intensive work will not disrupt the UI.
522         final HandlerThread thread = new HandlerThread("MusicPlayerHandler",
523                 android.os.Process.THREAD_PRIORITY_BACKGROUND);
524         thread.start();
525
526         // Initialize the handlers
527         mPlayerHandler = new MusicPlayerHandler(this, thread.getLooper());
528         mDelayedStopHandler = new DelayedHandler(this);
529
530         // Initialize the audio manager and register any headset controls for
531         // playback
532         mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
533         mMediaButtonReceiverComponent = new ComponentName(getPackageName(),
534                 MediaButtonIntentReceiver.class.getName());
535         mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
536
537         // Use the remote control APIs (if available) to set the playback state
538         setUpRemoteControlClient();
539
540         // Initialize the preferences
541         mPreferences = getSharedPreferences("Service", 0);
542         mCardId = getCardId();
543
544         registerExternalStorageListener();
545
546         // Initialize the media player
547         mPlayer = new MultiPlayer(this);
548         mPlayer.setHandler(mPlayerHandler);
549
550         // Initialize the intent filter and each action
551         final IntentFilter filter = new IntentFilter();
552         filter.addAction(SERVICECMD);
553         filter.addAction(TOGGLEPAUSE_ACTION);
554         filter.addAction(PAUSE_ACTION);
555         filter.addAction(STOP_ACTION);
556         filter.addAction(NEXT_ACTION);
557         filter.addAction(PREVIOUS_ACTION);
558         filter.addAction(REPEAT_ACTION);
559         filter.addAction(SHUFFLE_ACTION);
560         // Attach the broadcast listener
561         registerReceiver(mIntentReceiver, filter);
562
563         // Initialize the wake lock
564         final PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
565         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
566         mWakeLock.setReferenceCounted(false);
567
568         // Initialize the notification removal intent
569         final Intent killIntent = new Intent(this, MusicPlaybackService.class);
570         killIntent.setAction(KILL_NOTIFICATION);
571
572         mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
573         mKillNotificationIntent = PendingIntent.getService(this, 0, killIntent, 0);
574
575         // Bring the queue back
576         reloadQueue();
577         notifyChange(QUEUE_CHANGED);
578         notifyChange(META_CHANGED);
579
580         // Listen for the idle state
581         final Message message = mDelayedStopHandler.obtainMessage();
582         mDelayedStopHandler.sendMessageDelayed(message, IDLE_DELAY);
583     }
584
585     /**
586      * Initializes the remote control client
587      */
588     private void setUpRemoteControlClient() {
589         final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
590         mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);
591         mRemoteControlClient = new RemoteControlClient(
592                 PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
593                         PendingIntent.FLAG_UPDATE_CURRENT));
594         mAudioManager.registerRemoteControlClient(mRemoteControlClient);
595
596         // Flags for the media transport control that this client supports.
597         final int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
598                 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
599                 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
600                 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
601                 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
602                 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
603         mRemoteControlClient.setTransportControlFlags(flags);
604     }
605
606     /**
607      * {@inheritDoc}
608      */
609     @Override
610     public void onDestroy() {
611         super.onDestroy();
612         // Remove any sound effects
613         final Intent audioEffectsIntent = new Intent(
614                 AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
615         audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
616         audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
617         sendBroadcast(audioEffectsIntent);
618
619         // remove any pending alarms
620         mAlarmManager.cancel(mKillNotificationIntent);
621
622         // Release the player
623         mPlayer.release();
624         mPlayer = null;
625
626         // Remove the audio focus listener and lock screen controls
627         mAudioManager.abandonAudioFocus(mAudioFocusListener);
628         mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
629
630         // Remove any callbacks from the handlers
631         mDelayedStopHandler.removeCallbacksAndMessages(null);
632         mPlayerHandler.removeCallbacksAndMessages(null);
633
634         // Close the cursor
635         if (mCursor != null) {
636             mCursor.close();
637             mCursor = null;
638         }
639
640         // Unregister the mount listener
641         unregisterReceiver(mIntentReceiver);
642         if (mUnmountReceiver != null) {
643             unregisterReceiver(mUnmountReceiver);
644             mUnmountReceiver = null;
645         }
646
647         // Release the wake lock
648         mWakeLock.release();
649     }
650
651     /**
652      * {@inheritDoc}
653      */
654     @Override
655     public int onStartCommand(final Intent intent, final int flags, final int startId) {
656         mServiceStartId = startId;
657
658         if (intent != null) {
659             final String action = intent.getAction();
660
661             if (intent.hasExtra(NOW_IN_FOREGROUND)) {
662                 mAnyActivityInForeground = intent.getBooleanExtra(NOW_IN_FOREGROUND, false);
663                 updateNotification();
664             }
665
666             if (KILL_NOTIFICATION.equals(action)) {
667                 mNotificationHelper.killNotification();
668             } else {
669                 handleCommandIntent(intent);
670             }
671         }
672
673         // Make sure the service will shut down on its own if it was
674         // just started but not bound to and nothing is playing
675         mDelayedStopHandler.removeCallbacksAndMessages(null);
676         final Message msg = mDelayedStopHandler.obtainMessage();
677         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
678         return START_STICKY;
679     }
680
681     private void handleCommandIntent(Intent intent) {
682         final String action = intent.getAction();
683         final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
684
685         if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
686             gotoNext(true);
687         } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)) {
688             if (position() < 2000) {
689                 prev();
690             } else {
691                 seek(0);
692                 play();
693             }
694         } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
695             if (isPlaying()) {
696                 pause();
697                 mPausedByTransientLossOfFocus = false;
698             } else {
699                 play();
700             }
701         } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
702             pause();
703             mPausedByTransientLossOfFocus = false;
704         } else if (CMDPLAY.equals(command)) {
705             play();
706         } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
707             pause();
708             mPausedByTransientLossOfFocus = false;
709             seek(0);
710             mNotificationHelper.killNotification();
711         } else if (REPEAT_ACTION.equals(action)) {
712             cycleRepeat();
713         } else if (SHUFFLE_ACTION.equals(action)) {
714             cycleShuffle();
715         }
716     }
717
718     /**
719      * Updates the notification, considering the current play and activity state
720      */
721     private void updateNotification() {
722         if (!mAnyActivityInForeground && isPlaying()) {
723             mAlarmManager.cancel(mKillNotificationIntent);
724             mNotificationHelper.buildNotification(getAlbumName(), getArtistName(),
725                     getTrackName(), getAlbumId(), getAlbumArt(), isPlaying());
726         } else if (mAnyActivityInForeground) {
727             mNotificationHelper.killNotification();
728         }
729     }
730
731     /**
732      * @return A card ID used to save and restore playlists, i.e., the queue.
733      */
734     private int getCardId() {
735         final ContentResolver resolver = getContentResolver();
736         Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
737                 null, null);
738         int mCardId = -1;
739         if (cursor != null && cursor.moveToFirst()) {
740             mCardId = cursor.getInt(0);
741             cursor.close();
742             cursor = null;
743         }
744         return mCardId;
745     }
746
747     /**
748      * Called when we receive a ACTION_MEDIA_EJECT notification.
749      *
750      * @param storagePath The path to mount point for the removed media
751      */
752     public void closeExternalStorageFiles(final String storagePath) {
753         stop(true);
754         notifyChange(QUEUE_CHANGED);
755         notifyChange(META_CHANGED);
756     }
757
758     /**
759      * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
760      * intent will call closeExternalStorageFiles() if the external media is
761      * going to be ejected, so applications can clean up any files they have
762      * open.
763      */
764     public void registerExternalStorageListener() {
765         if (mUnmountReceiver == null) {
766             mUnmountReceiver = new BroadcastReceiver() {
767
768                 /**
769                  * {@inheritDoc}
770                  */
771                 @Override
772                 public void onReceive(final Context context, final Intent intent) {
773                     final String action = intent.getAction();
774                     if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
775                         saveQueue(true);
776                         mQueueIsSaveable = false;
777                         closeExternalStorageFiles(intent.getData().getPath());
778                     } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
779                         mMediaMountedCount++;
780                         mCardId = getCardId();
781                         reloadQueue();
782                         mQueueIsSaveable = true;
783                         notifyChange(QUEUE_CHANGED);
784                         notifyChange(META_CHANGED);
785                     }
786                 }
787             };
788             final IntentFilter filter = new IntentFilter();
789             filter.addAction(Intent.ACTION_MEDIA_EJECT);
790             filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
791             filter.addDataScheme("file");
792             registerReceiver(mUnmountReceiver, filter);
793         }
794     }
795
796     /**
797      * Changes the notification buttons to a paused state and beging the
798      * countdown to calling {@code #stopForeground(true)}
799      */
800     private void gotoIdleState() {
801         mDelayedStopHandler.removeCallbacksAndMessages(null);
802         final Message msg = mDelayedStopHandler.obtainMessage();
803         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
804         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
805                 SystemClock.elapsedRealtime() + IDLE_DELAY, mKillNotificationIntent);
806     }
807
808     /**
809      * Stops playback
810      *
811      * @param goToIdle True to go to the idle state, false otherwise
812      */
813     private void stop(final boolean goToIdle) {
814         if (mPlayer.isInitialized()) {
815             mPlayer.stop();
816         }
817         mFileToPlay = null;
818         if (mCursor != null) {
819             mCursor.close();
820             mCursor = null;
821         }
822         if (goToIdle) {
823             gotoIdleState();
824             mIsSupposedToBePlaying = false;
825         } else {
826             stopForeground(false);
827         }
828     }
829
830     /**
831      * Removes the range of tracks specified from the play list. If a file
832      * within the range is the file currently being played, playback will move
833      * to the next file after the range.
834      *
835      * @param first The first file to be removed
836      * @param last The last file to be removed
837      * @return the number of tracks deleted
838      */
839     private int removeTracksInternal(int first, int last) {
840         synchronized (this) {
841             if (last < first) {
842                 return 0;
843             } else if (first < 0) {
844                 first = 0;
845             } else if (last >= mPlayListLen) {
846                 last = mPlayListLen - 1;
847             }
848
849             boolean gotonext = false;
850             if (first <= mPlayPos && mPlayPos <= last) {
851                 mPlayPos = first;
852                 gotonext = true;
853             } else if (mPlayPos > last) {
854                 mPlayPos -= last - first + 1;
855             }
856             final int num = mPlayListLen - last - 1;
857             for (int i = 0; i < num; i++) {
858                 mPlayList[first + i] = mPlayList[last + 1 + i];
859             }
860             mPlayListLen -= last - first + 1;
861
862             if (gotonext) {
863                 if (mPlayListLen == 0) {
864                     stop(true);
865                     mPlayPos = -1;
866                     if (mCursor != null) {
867                         mCursor.close();
868                         mCursor = null;
869                     }
870                 } else {
871                     if (mPlayPos >= mPlayListLen) {
872                         mPlayPos = 0;
873                     }
874                     final boolean wasPlaying = isPlaying();
875                     stop(false);
876                     openCurrentAndNext();
877                     if (wasPlaying) {
878                         play();
879                     }
880                 }
881                 notifyChange(META_CHANGED);
882             }
883             return last - first + 1;
884         }
885     }
886
887     /**
888      * Adds a list to the playlist
889      *
890      * @param list The list to add
891      * @param position The position to place the tracks
892      */
893     private void addToPlayList(final long[] list, int position) {
894         final int addlen = list.length;
895         if (position < 0) {
896             mPlayListLen = 0;
897             position = 0;
898         }
899         ensurePlayListCapacity(mPlayListLen + addlen);
900         if (position > mPlayListLen) {
901             position = mPlayListLen;
902         }
903
904         final int tailsize = mPlayListLen - position;
905         for (int i = tailsize; i > 0; i--) {
906             mPlayList[position + i] = mPlayList[position + i - addlen];
907         }
908
909         for (int i = 0; i < addlen; i++) {
910             mPlayList[position + i] = list[i];
911         }
912         mPlayListLen += addlen;
913         if (mPlayListLen == 0) {
914             mCursor.close();
915             mCursor = null;
916             notifyChange(META_CHANGED);
917         }
918     }
919
920     /**
921      * @param lid The list ID
922      * @return The cursor used for a specific ID
923      */
924     private Cursor getCursorForId(final long id) {
925         final Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
926                 PROJECTION, "_id=" + id, null, null);
927         if (c != null) {
928             c.moveToFirst();
929         }
930         return c;
931     }
932
933     /**
934      * Called to open a new file as the current track and prepare the next for
935      * playback
936      */
937     private void openCurrentAndNext() {
938         openCurrentAndMaybeNext(true);
939     }
940
941     /**
942      * Called to open a new file as the current track and prepare the next for
943      * playback
944      *
945      * @param openNext True to prepare the next track for playback, false
946      *            otherwise.
947      */
948     private void openCurrentAndMaybeNext(final boolean openNext) {
949         synchronized (this) {
950             if (mCursor != null) {
951                 mCursor.close();
952                 mCursor = null;
953             }
954
955             if (mPlayListLen == 0) {
956                 return;
957             }
958             stop(false);
959
960             mCursor = getCursorForId(mPlayList[mPlayPos]);
961             while (true) {
962                 if (mCursor != null
963                         && mCursor.getCount() != 0
964                         && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
965                                 + mCursor.getLong(IDCOLIDX))) {
966                     break;
967                 }
968                 // if we get here then opening the file failed. We can close the
969                 // cursor now, because
970                 // we're either going to create a new one next, or stop trying
971                 if (mCursor != null) {
972                     mCursor.close();
973                     mCursor = null;
974                 }
975                 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
976                     final int pos = getNextPosition(false);
977                     if (pos < 0) {
978                         gotoIdleState();
979                         if (mIsSupposedToBePlaying) {
980                             mIsSupposedToBePlaying = false;
981                             notifyChange(PLAYSTATE_CHANGED);
982                         }
983                         return;
984                     }
985                     mPlayPos = pos;
986                     stop(false);
987                     mPlayPos = pos;
988                     mCursor = getCursorForId(mPlayList[mPlayPos]);
989                 } else {
990                     mOpenFailedCounter = 0;
991                     gotoIdleState();
992                     if (mIsSupposedToBePlaying) {
993                         mIsSupposedToBePlaying = false;
994                         notifyChange(PLAYSTATE_CHANGED);
995                     }
996                     return;
997                 }
998             }
999             if (openNext) {
1000                 setNextTrack();
1001             }
1002         }
1003     }
1004
1005     /**
1006      * @param force True to force the player onto the track next, false
1007      *            otherwise.
1008      * @return The next position to play.
1009      */
1010     private int getNextPosition(final boolean force) {
1011         if (!force && mRepeatMode == REPEAT_CURRENT) {
1012             if (mPlayPos < 0) {
1013                 return 0;
1014             }
1015             return mPlayPos;
1016         } else if (mShuffleMode == SHUFFLE_NORMAL) {
1017             if (mPlayPos >= 0) {
1018                 mHistory.add(mPlayPos);
1019             }
1020             if (mHistory.size() > MAX_HISTORY_SIZE) {
1021                 mHistory.remove(0);
1022             }
1023             final int numTracks = mPlayListLen;
1024             final int[] tracks = new int[numTracks];
1025             for (int i = 0; i < numTracks; i++) {
1026                 tracks[i] = i;
1027             }
1028
1029             final int numHistory = mHistory.size();
1030             int numUnplayed = numTracks;
1031             for (int i = 0; i < numHistory; i++) {
1032                 final int idx = mHistory.get(i).intValue();
1033                 if (idx < numTracks && tracks[idx] >= 0) {
1034                     numUnplayed--;
1035                     tracks[idx] = -1;
1036                 }
1037             }
1038             if (numUnplayed <= 0) {
1039                 if (mRepeatMode == REPEAT_ALL || force) {
1040                     numUnplayed = numTracks;
1041                     for (int i = 0; i < numTracks; i++) {
1042                         tracks[i] = i;
1043                     }
1044                 } else {
1045                     return -1;
1046                 }
1047             }
1048             int skip = 0;
1049             if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
1050                 skip = mShuffler.nextInt(numUnplayed);
1051             }
1052             int cnt = -1;
1053             while (true) {
1054                 while (tracks[++cnt] < 0) {
1055                     ;
1056                 }
1057                 skip--;
1058                 if (skip < 0) {
1059                     break;
1060                 }
1061             }
1062             return cnt;
1063         } else if (mShuffleMode == SHUFFLE_AUTO) {
1064             doAutoShuffleUpdate();
1065             return mPlayPos + 1;
1066         } else {
1067             if (mPlayPos >= mPlayListLen - 1) {
1068                 if (mRepeatMode == REPEAT_NONE && !force) {
1069                     return -1;
1070                 } else if (mRepeatMode == REPEAT_ALL || force) {
1071                     return 0;
1072                 }
1073                 return -1;
1074             } else {
1075                 return mPlayPos + 1;
1076             }
1077         }
1078     }
1079
1080     /**
1081      * Sets the track track to be played
1082      */
1083     private void setNextTrack() {
1084         mNextPlayPos = getNextPosition(false);
1085         if (mNextPlayPos >= 0 && mPlayList != null) {
1086             final long id = mPlayList[mNextPlayPos];
1087             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1088         }
1089     }
1090
1091     /**
1092      * Creates a shuffled playlist used for party mode
1093      */
1094     private boolean makeAutoShuffleList() {
1095         Cursor cursor = null;
1096         try {
1097             cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1098                     new String[] {
1099                         MediaStore.Audio.Media._ID
1100                     }, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
1101             if (cursor == null || cursor.getCount() == 0) {
1102                 return false;
1103             }
1104             final int len = cursor.getCount();
1105             final long[] list = new long[len];
1106             for (int i = 0; i < len; i++) {
1107                 cursor.moveToNext();
1108                 list[i] = cursor.getLong(0);
1109             }
1110             mAutoShuffleList = list;
1111             return true;
1112         } catch (final RuntimeException e) {
1113         } finally {
1114             if (cursor != null) {
1115                 cursor.close();
1116                 cursor = null;
1117             }
1118         }
1119         return false;
1120     }
1121
1122     /**
1123      * Creates the party shuffle playlist
1124      */
1125     private void doAutoShuffleUpdate() {
1126         boolean notify = false;
1127         if (mPlayPos > 10) {
1128             removeTracks(0, mPlayPos - 9);
1129             notify = true;
1130         }
1131         final int toAdd = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1132         for (int i = 0; i < toAdd; i++) {
1133             int lookback = mHistory.size();
1134             int idx = -1;
1135             while (true) {
1136                 idx = mShuffler.nextInt(mAutoShuffleList.length);
1137                 if (!wasRecentlyUsed(idx, lookback)) {
1138                     break;
1139                 }
1140                 lookback /= 2;
1141             }
1142             mHistory.add(idx);
1143             if (mHistory.size() > MAX_HISTORY_SIZE) {
1144                 mHistory.remove(0);
1145             }
1146             ensurePlayListCapacity(mPlayListLen + 1);
1147             mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
1148             notify = true;
1149         }
1150         if (notify) {
1151             notifyChange(QUEUE_CHANGED);
1152         }
1153     }
1154
1155     /**/
1156     private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
1157         if (lookbacksize == 0) {
1158             return false;
1159         }
1160         final int histsize = mHistory.size();
1161         if (histsize < lookbacksize) {
1162             lookbacksize = histsize;
1163         }
1164         final int maxidx = histsize - 1;
1165         for (int i = 0; i < lookbacksize; i++) {
1166             final long entry = mHistory.get(maxidx - i);
1167             if (entry == idx) {
1168                 return true;
1169             }
1170         }
1171         return false;
1172     }
1173
1174     /**
1175      * Makes sure the playlist has enough space to hold all of the songs
1176      *
1177      * @param size The size of the playlist
1178      */
1179     private void ensurePlayListCapacity(final int size) {
1180         if (mPlayList == null || size > mPlayList.length) {
1181             // reallocate at 2x requested size so we don't
1182             // need to grow and copy the array for every
1183             // insert
1184             final long[] newlist = new long[size * 2];
1185             final int len = mPlayList != null ? mPlayList.length : mPlayListLen;
1186             for (int i = 0; i < len; i++) {
1187                 newlist[i] = mPlayList[i];
1188             }
1189             mPlayList = newlist;
1190         }
1191         // FIXME: shrink the array when the needed size is much smaller
1192         // than the allocated size
1193     }
1194
1195     /**
1196      * Notify the change-receivers that something has changed.
1197      */
1198     private void notifyChange(final String what) {
1199         final Intent intent = new Intent(what);
1200         intent.putExtra("id", getAudioId());
1201         intent.putExtra("artist", getArtistName());
1202         intent.putExtra("album", getAlbumName());
1203         intent.putExtra("track", getTrackName());
1204         intent.putExtra("playing", isPlaying());
1205         intent.putExtra("isfavorite", isFavorite());
1206         sendStickyBroadcast(intent);
1207
1208         final Intent musicIntent = new Intent(intent);
1209         musicIntent.setAction(what.replace(APOLLO_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
1210         sendStickyBroadcast(musicIntent);
1211
1212         // Update the lockscreen controls
1213         updateRemoteControlClient(what);
1214
1215         if (what.equals(META_CHANGED)) {
1216             // Increase the play count for favorite songs.
1217             if (mFavoritesCache.getSongId(getAudioId()) != null) {
1218                 mFavoritesCache.addSongId(getAudioId(), getTrackName(), getAlbumName(),
1219                         getArtistName());
1220             }
1221             // Add the track to the recently played list.
1222             mRecentsCache.addAlbumId(getAlbumId(), getAlbumName(), getArtistName(),
1223                     MusicUtils.getSongCountForAlbum(this, getAlbumName()),
1224                     MusicUtils.getReleaseDateForAlbum(this, getAlbumName()));
1225         } else if (what.equals(QUEUE_CHANGED)) {
1226             saveQueue(true);
1227         } else {
1228             saveQueue(false);
1229         }
1230
1231         if (what.equals(PLAYSTATE_CHANGED)) {
1232             mNotificationHelper.updatePlayState(isPlaying());
1233         }
1234
1235         // Update the app-widgets
1236         mAppWidgetSmall.notifyChange(this, what);
1237         mAppWidgetLarge.notifyChange(this, what);
1238         mAppWidgetLargeAlternate.notifyChange(this, what);
1239         mRecentWidgetProvider.notifyChange(this, what);
1240     }
1241
1242     /**
1243      * Updates the lockscreen controls.
1244      *
1245      * @param what The broadcast
1246      */
1247     private void updateRemoteControlClient(final String what) {
1248         if (what.equals(PLAYSTATE_CHANGED)) {
1249             mRemoteControlClient.setPlaybackState(mIsSupposedToBePlaying
1250                     ? RemoteControlClient.PLAYSTATE_PLAYING
1251                     : RemoteControlClient.PLAYSTATE_PAUSED);
1252         } else if (what.equals(META_CHANGED)) {
1253             Bitmap albumArt = getAlbumArt();
1254             if (albumArt != null) {
1255                 // RemoteControlClient wants to recycle the bitmaps thrown at it, so we need
1256                 // to make sure not to hand out our cache copy
1257                 Bitmap.Config config = albumArt.getConfig();
1258                 if (config == null) {
1259                     config = Bitmap.Config.ARGB_8888;
1260                 }
1261                 albumArt = albumArt.copy(config, false);
1262             }
1263             mRemoteControlClient
1264                     .editMetadata(true)
1265                     .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName())
1266                     .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName())
1267                     .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName())
1268                     .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration())
1269                     .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt)
1270                     .apply();
1271         }
1272     }
1273
1274     /**
1275      * Saves the queue
1276      *
1277      * @param full True if the queue is full
1278      */
1279     private void saveQueue(final boolean full) {
1280         if (!mQueueIsSaveable) {
1281             return;
1282         }
1283
1284         final SharedPreferences.Editor editor = mPreferences.edit();
1285         if (full) {
1286             final StringBuilder q = new StringBuilder();
1287             int len = mPlayListLen;
1288             for (int i = 0; i < len; i++) {
1289                 long n = mPlayList[i];
1290                 if (n < 0) {
1291                     continue;
1292                 } else if (n == 0) {
1293                     q.append("0;");
1294                 } else {
1295                     while (n != 0) {
1296                         final int digit = (int)(n & 0xf);
1297                         n >>>= 4;
1298                         q.append(HEX_DIGITS[digit]);
1299                     }
1300                     q.append(";");
1301                 }
1302             }
1303             editor.putString("queue", q.toString());
1304             editor.putInt("cardid", mCardId);
1305             if (mShuffleMode != SHUFFLE_NONE) {
1306                 len = mHistory.size();
1307                 q.setLength(0);
1308                 for (int i = 0; i < len; i++) {
1309                     int n = mHistory.get(i);
1310                     if (n == 0) {
1311                         q.append("0;");
1312                     } else {
1313                         while (n != 0) {
1314                             final int digit = n & 0xf;
1315                             n >>>= 4;
1316                             q.append(HEX_DIGITS[digit]);
1317                         }
1318                         q.append(";");
1319                     }
1320                 }
1321                 editor.putString("history", q.toString());
1322             }
1323         }
1324         editor.putInt("curpos", mPlayPos);
1325         if (mPlayer.isInitialized()) {
1326             editor.putLong("seekpos", mPlayer.position());
1327         }
1328         editor.putInt("repeatmode", mRepeatMode);
1329         editor.putInt("shufflemode", mShuffleMode);
1330         editor.apply();
1331     }
1332
1333     /**
1334      * Reloads the queue as the user left it the last time they stopped using
1335      * Apollo
1336      */
1337     private void reloadQueue() {
1338         String q = null;
1339         int id = mCardId;
1340         if (mPreferences.contains("cardid")) {
1341             id = mPreferences.getInt("cardid", ~mCardId);
1342         }
1343         if (id == mCardId) {
1344             q = mPreferences.getString("queue", "");
1345         }
1346         int qlen = q != null ? q.length() : 0;
1347         if (qlen > 1) {
1348             int plen = 0;
1349             int n = 0;
1350             int shift = 0;
1351             for (int i = 0; i < qlen; i++) {
1352                 final char c = q.charAt(i);
1353                 if (c == ';') {
1354                     ensurePlayListCapacity(plen + 1);
1355                     mPlayList[plen] = n;
1356                     plen++;
1357                     n = 0;
1358                     shift = 0;
1359                 } else {
1360                     if (c >= '0' && c <= '9') {
1361                         n += c - '0' << shift;
1362                     } else if (c >= 'a' && c <= 'f') {
1363                         n += 10 + c - 'a' << shift;
1364                     } else {
1365                         plen = 0;
1366                         break;
1367                     }
1368                     shift += 4;
1369                 }
1370             }
1371             mPlayListLen = plen;
1372             final int pos = mPreferences.getInt("curpos", 0);
1373             if (pos < 0 || pos >= mPlayListLen) {
1374                 mPlayListLen = 0;
1375                 return;
1376             }
1377             mPlayPos = pos;
1378             Cursor mCursor = getContentResolver().query(
1379                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[] {
1380                         "_id"
1381                     }, "_id=" + mPlayList[mPlayPos], null, null);
1382             if (mCursor == null || mCursor.getCount() == 0) {
1383                 SystemClock.sleep(3000);
1384                 mCursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1385                         PROJECTION, "_id=" + mPlayList[mPlayPos], null, null);
1386             }
1387             if (mCursor != null) {
1388                 mCursor.close();
1389                 mCursor = null;
1390             }
1391             mOpenFailedCounter = 20;
1392             openCurrentAndNext();
1393             if (!mPlayer.isInitialized()) {
1394                 mPlayListLen = 0;
1395                 return;
1396             }
1397
1398             final long seekpos = mPreferences.getLong("seekpos", 0);
1399             seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
1400
1401             int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
1402             if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
1403                 repmode = REPEAT_NONE;
1404             }
1405             mRepeatMode = repmode;
1406
1407             int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
1408             if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
1409                 shufmode = SHUFFLE_NONE;
1410             }
1411             if (shufmode != SHUFFLE_NONE) {
1412                 q = mPreferences.getString("history", "");
1413                 qlen = q != null ? q.length() : 0;
1414                 if (qlen > 1) {
1415                     plen = 0;
1416                     n = 0;
1417                     shift = 0;
1418                     mHistory.clear();
1419                     for (int i = 0; i < qlen; i++) {
1420                         final char c = q.charAt(i);
1421                         if (c == ';') {
1422                             if (n >= mPlayListLen) {
1423                                 mHistory.clear();
1424                                 break;
1425                             }
1426                             mHistory.add(n);
1427                             n = 0;
1428                             shift = 0;
1429                         } else {
1430                             if (c >= '0' && c <= '9') {
1431                                 n += c - '0' << shift;
1432                             } else if (c >= 'a' && c <= 'f') {
1433                                 n += 10 + c - 'a' << shift;
1434                             } else {
1435                                 mHistory.clear();
1436                                 break;
1437                             }
1438                             shift += 4;
1439                         }
1440                     }
1441                 }
1442             }
1443             if (shufmode == SHUFFLE_AUTO) {
1444                 if (!makeAutoShuffleList()) {
1445                     shufmode = SHUFFLE_NONE;
1446                 }
1447             }
1448             mShuffleMode = shufmode;
1449         }
1450     }
1451
1452     /**
1453      * Opens a file and prepares it for playback
1454      *
1455      * @param path The path of the file to open
1456      */
1457     public boolean openFile(final String path) {
1458         synchronized (this) {
1459             if (path == null) {
1460                 return false;
1461             }
1462
1463             // If mCursor is null, try to associate path with a database cursor
1464             if (mCursor == null) {
1465                 final ContentResolver resolver = getContentResolver();
1466                 Uri uri;
1467                 String where;
1468                 String selectionArgs[];
1469                 if (path.startsWith("content://media/")) {
1470                     uri = Uri.parse(path);
1471                     where = null;
1472                     selectionArgs = null;
1473                 } else {
1474                     uri = MediaStore.Audio.Media.getContentUriForPath(path);
1475                     where = MediaStore.Audio.Media.DATA + "=?";
1476                     selectionArgs = new String[] {
1477                         path
1478                     };
1479                 }
1480                 try {
1481                     mCursor = resolver.query(uri, PROJECTION, where, selectionArgs, null);
1482                     if (mCursor != null) {
1483                         if (mCursor.getCount() == 0) {
1484                             mCursor.close();
1485                             mCursor = null;
1486                         } else {
1487                             mCursor.moveToNext();
1488                             ensurePlayListCapacity(1);
1489                             mPlayListLen = 1;
1490                             mPlayList[0] = mCursor.getLong(IDCOLIDX);
1491                             mPlayPos = 0;
1492                         }
1493                     }
1494                 } catch (final UnsupportedOperationException ex) {
1495                 }
1496             }
1497             mFileToPlay = path;
1498             mPlayer.setDataSource(mFileToPlay);
1499             if (mPlayer.isInitialized()) {
1500                 mOpenFailedCounter = 0;
1501                 return true;
1502             }
1503             stop(true);
1504             return false;
1505         }
1506     }
1507
1508     /**
1509      * Returns the audio session ID
1510      *
1511      * @return The current media player audio session ID
1512      */
1513     public int getAudioSessionId() {
1514         synchronized (this) {
1515             return mPlayer.getAudioSessionId();
1516         }
1517     }
1518
1519     /**
1520      * Indicates if the media storeage device has been mounted or not
1521      *
1522      * @return 1 if Intent.ACTION_MEDIA_MOUNTED is called, 0 otherwise
1523      */
1524     public int getMediaMountedCount() {
1525         return mMediaMountedCount;
1526     }
1527
1528     /**
1529      * Returns the shuffle mode
1530      *
1531      * @return The current shuffle mode (all, party, none)
1532      */
1533     public int getShuffleMode() {
1534         return mShuffleMode;
1535     }
1536
1537     /**
1538      * Returns the repeat mode
1539      *
1540      * @return The current repeat mode (all, one, none)
1541      */
1542     public int getRepeatMode() {
1543         return mRepeatMode;
1544     }
1545
1546     /**
1547      * Removes all instances of the track with the given ID from the playlist.
1548      *
1549      * @param id The id to be removed
1550      * @return how many instances of the track were removed
1551      */
1552     public int removeTrack(final long id) {
1553         int numremoved = 0;
1554         synchronized (this) {
1555             for (int i = 0; i < mPlayListLen; i++) {
1556                 if (mPlayList[i] == id) {
1557                     numremoved += removeTracksInternal(i, i);
1558                     i--;
1559                 }
1560             }
1561         }
1562         if (numremoved > 0) {
1563             notifyChange(QUEUE_CHANGED);
1564         }
1565         return numremoved;
1566     }
1567
1568     /**
1569      * Removes the range of tracks specified from the play list. If a file
1570      * within the range is the file currently being played, playback will move
1571      * to the next file after the range.
1572      *
1573      * @param first The first file to be removed
1574      * @param last The last file to be removed
1575      * @return the number of tracks deleted
1576      */
1577     public int removeTracks(final int first, final int last) {
1578         final int numremoved = removeTracksInternal(first, last);
1579         if (numremoved > 0) {
1580             notifyChange(QUEUE_CHANGED);
1581         }
1582         return numremoved;
1583     }
1584
1585     /**
1586      * Returns the position in the queue
1587      *
1588      * @return the current position in the queue
1589      */
1590     public int getQueuePosition() {
1591         synchronized (this) {
1592             return mPlayPos;
1593         }
1594     }
1595
1596     /**
1597      * Returns the path to current song
1598      *
1599      * @return The path to the current song
1600      */
1601     public String getPath() {
1602         synchronized (this) {
1603             if (mCursor == null) {
1604                 return null;
1605             }
1606             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
1607         }
1608     }
1609
1610     /**
1611      * Returns the album name
1612      *
1613      * @return The current song album Name
1614      */
1615     public String getAlbumName() {
1616         synchronized (this) {
1617             if (mCursor == null) {
1618                 return null;
1619             }
1620             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
1621         }
1622     }
1623
1624     /**
1625      * Returns the song name
1626      *
1627      * @return The current song name
1628      */
1629     public String getTrackName() {
1630         synchronized (this) {
1631             if (mCursor == null) {
1632                 return null;
1633             }
1634             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
1635         }
1636     }
1637
1638     /**
1639      * Returns the artist name
1640      *
1641      * @return The current song artist name
1642      */
1643     public String getArtistName() {
1644         synchronized (this) {
1645             if (mCursor == null) {
1646                 return null;
1647             }
1648             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));
1649         }
1650     }
1651
1652     /**
1653      * Returns the album ID
1654      *
1655      * @return The current song album ID
1656      */
1657     public long getAlbumId() {
1658         synchronized (this) {
1659             if (mCursor == null) {
1660                 return -1;
1661             }
1662             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
1663         }
1664     }
1665
1666     /**
1667      * Returns the artist ID
1668      *
1669      * @return The current song artist ID
1670      */
1671     public long getArtistId() {
1672         synchronized (this) {
1673             if (mCursor == null) {
1674                 return -1;
1675             }
1676             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));
1677         }
1678     }
1679
1680     /**
1681      * Returns the current audio ID
1682      *
1683      * @return The current track ID
1684      */
1685     public long getAudioId() {
1686         synchronized (this) {
1687             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1688                 return mPlayList[mPlayPos];
1689             }
1690         }
1691         return -1;
1692     }
1693
1694     /**
1695      * Seeks the current track to a specific time
1696      *
1697      * @param position The time to seek to
1698      * @return The time to play the track at
1699      */
1700     public long seek(long position) {
1701         if (mPlayer.isInitialized()) {
1702             if (position < 0) {
1703                 position = 0;
1704             } else if (position > mPlayer.duration()) {
1705                 position = mPlayer.duration();
1706             }
1707             return mPlayer.seek(position);
1708         }
1709         return -1;
1710     }
1711
1712     /**
1713      * Returns the current position in time of the currenttrack
1714      *
1715      * @return The current playback position in miliseconds
1716      */
1717     public long position() {
1718         if (mPlayer.isInitialized()) {
1719             return mPlayer.position();
1720         }
1721         return -1;
1722     }
1723
1724     /**
1725      * Returns the full duration of the current track
1726      *
1727      * @return The duration of the current track in miliseconds
1728      */
1729     public long duration() {
1730         if (mPlayer.isInitialized()) {
1731             return mPlayer.duration();
1732         }
1733         return -1;
1734     }
1735
1736     /**
1737      * Returns the queue
1738      *
1739      * @return The queue as a long[]
1740      */
1741     public long[] getQueue() {
1742         synchronized (this) {
1743             final int len = mPlayListLen;
1744             final long[] list = new long[len];
1745             for (int i = 0; i < len; i++) {
1746                 list[i] = mPlayList[i];
1747             }
1748             return list;
1749         }
1750     }
1751
1752     /**
1753      * @return True if music is playing, false otherwise
1754      */
1755     public boolean isPlaying() {
1756         return mIsSupposedToBePlaying;
1757     }
1758
1759     /**
1760      * True if the current track is a "favorite", false otherwise
1761      */
1762     public boolean isFavorite() {
1763         if (mFavoritesCache != null) {
1764             synchronized (this) {
1765                 final Long id = mFavoritesCache.getSongId(getAudioId());
1766                 return id != null ? true : false;
1767             }
1768         }
1769         return false;
1770     }
1771
1772     /**
1773      * Opens a list for playback
1774      *
1775      * @param list The list of tracks to open
1776      * @param position The position to start playback at
1777      */
1778     public void open(final long[] list, final int position) {
1779         synchronized (this) {
1780             if (mShuffleMode == SHUFFLE_AUTO) {
1781                 mShuffleMode = SHUFFLE_NORMAL;
1782             }
1783             final long oldId = getAudioId();
1784             final int listlength = list.length;
1785             boolean newlist = true;
1786             if (mPlayListLen == listlength) {
1787                 newlist = false;
1788                 for (int i = 0; i < listlength; i++) {
1789                     if (list[i] != mPlayList[i]) {
1790                         newlist = true;
1791                         break;
1792                     }
1793                 }
1794             }
1795             if (newlist) {
1796                 addToPlayList(list, -1);
1797                 notifyChange(QUEUE_CHANGED);
1798             }
1799             if (position >= 0) {
1800                 mPlayPos = position;
1801             } else {
1802                 mPlayPos = mShuffler.nextInt(mPlayListLen);
1803             }
1804             mHistory.clear();
1805             openCurrentAndNext();
1806             if (oldId != getAudioId()) {
1807                 notifyChange(META_CHANGED);
1808             }
1809         }
1810     }
1811
1812     /**
1813      * Stops playback.
1814      */
1815     public void stop() {
1816         stop(true);
1817     }
1818
1819     /**
1820      * Resumes or starts playback.
1821      */
1822     public void play() {
1823         int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
1824                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1825         if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1826             return;
1827         }
1828
1829         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
1830                 MediaButtonIntentReceiver.class.getName()));
1831
1832         if (mPlayer.isInitialized()) {
1833             final long duration = mPlayer.duration();
1834             if (mRepeatMode != REPEAT_CURRENT && duration > 2000
1835                     && mPlayer.position() >= duration - 2000) {
1836                 gotoNext(true);
1837             }
1838
1839             mPlayer.start();
1840             mPlayerHandler.removeMessages(FADEDOWN);
1841             mPlayerHandler.sendEmptyMessage(FADEUP);
1842
1843             if (!mIsSupposedToBePlaying) {
1844                 mIsSupposedToBePlaying = true;
1845                 notifyChange(PLAYSTATE_CHANGED);
1846             }
1847
1848             updateNotification();
1849         } else if (mPlayListLen <= 0) {
1850             setShuffleMode(SHUFFLE_AUTO);
1851         }
1852     }
1853
1854     /**
1855      * Temporarily pauses playback.
1856      */
1857     public void pause() {
1858         synchronized (this) {
1859             mPlayerHandler.removeMessages(FADEUP);
1860             if (mIsSupposedToBePlaying) {
1861                 mPlayer.pause();
1862                 gotoIdleState();
1863                 mIsSupposedToBePlaying = false;
1864                 notifyChange(PLAYSTATE_CHANGED);
1865             }
1866         }
1867     }
1868
1869     /**
1870      * Changes from the current track to the next track
1871      */
1872     public void gotoNext(final boolean force) {
1873         synchronized (this) {
1874             if (mPlayListLen <= 0) {
1875                 return;
1876             }
1877             final int pos = getNextPosition(force);
1878             if (pos < 0) {
1879                 gotoIdleState();
1880                 if (mIsSupposedToBePlaying) {
1881                     mIsSupposedToBePlaying = false;
1882                     notifyChange(PLAYSTATE_CHANGED);
1883                 }
1884                 return;
1885             }
1886             mPlayPos = pos;
1887             stop(false);
1888             mPlayPos = pos;
1889             openCurrentAndNext();
1890             play();
1891             notifyChange(META_CHANGED);
1892         }
1893     }
1894
1895     /**
1896      * Changes from the current track to the previous played track
1897      */
1898     public void prev() {
1899         synchronized (this) {
1900             if (mShuffleMode == SHUFFLE_NORMAL) {
1901                 // Go to previously-played track and remove it from the history
1902                 final int histsize = mHistory.size();
1903                 if (histsize == 0) {
1904                     return;
1905                 }
1906                 final Integer pos = mHistory.remove(histsize - 1);
1907                 mPlayPos = pos.intValue();
1908             } else {
1909                 if (mPlayPos > 0) {
1910                     mPlayPos--;
1911                 } else {
1912                     mPlayPos = mPlayListLen - 1;
1913                 }
1914             }
1915             stop(false);
1916             openCurrent();
1917             play();
1918             notifyChange(META_CHANGED);
1919         }
1920     }
1921
1922     /**
1923      * We don't want to open the current and next track when the user is using
1924      * the {@code #prev()} method because they won't be able to travel back to
1925      * the previously listened track if they're shuffling.
1926      */
1927     private void openCurrent() {
1928         openCurrentAndMaybeNext(false);
1929     }
1930
1931     /**
1932      * Toggles the current song as a favorite.
1933      */
1934     public void toggleFavorite() {
1935         if (mFavoritesCache != null) {
1936             synchronized (this) {
1937                 mFavoritesCache.toggleSong(getAudioId(), getTrackName(), getAlbumName(),
1938                         getArtistName());
1939             }
1940         }
1941     }
1942
1943     /**
1944      * Moves an item in the queue from one position to another
1945      *
1946      * @param from The position the item is currently at
1947      * @param to The position the item is being moved to
1948      */
1949     public void moveQueueItem(int index1, int index2) {
1950         synchronized (this) {
1951             if (index1 >= mPlayListLen) {
1952                 index1 = mPlayListLen - 1;
1953             }
1954             if (index2 >= mPlayListLen) {
1955                 index2 = mPlayListLen - 1;
1956             }
1957             if (index1 < index2) {
1958                 final long tmp = mPlayList[index1];
1959                 for (int i = index1; i < index2; i++) {
1960                     mPlayList[i] = mPlayList[i + 1];
1961                 }
1962                 mPlayList[index2] = tmp;
1963                 if (mPlayPos == index1) {
1964                     mPlayPos = index2;
1965                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
1966                     mPlayPos--;
1967                 }
1968             } else if (index2 < index1) {
1969                 final long tmp = mPlayList[index1];
1970                 for (int i = index1; i > index2; i--) {
1971                     mPlayList[i] = mPlayList[i - 1];
1972                 }
1973                 mPlayList[index2] = tmp;
1974                 if (mPlayPos == index1) {
1975                     mPlayPos = index2;
1976                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
1977                     mPlayPos++;
1978                 }
1979             }
1980             notifyChange(QUEUE_CHANGED);
1981         }
1982     }
1983
1984     /**
1985      * Sets the repeat mode
1986      *
1987      * @param repeatmode The repeat mode to use
1988      */
1989     public void setRepeatMode(final int repeatmode) {
1990         synchronized (this) {
1991             mRepeatMode = repeatmode;
1992             setNextTrack();
1993             saveQueue(false);
1994             notifyChange(REPEATMODE_CHANGED);
1995         }
1996     }
1997
1998     /**
1999      * Sets the shuffle mode
2000      *
2001      * @param shufflemode The shuffle mode to use
2002      */
2003     public void setShuffleMode(final int shufflemode) {
2004         synchronized (this) {
2005             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
2006                 return;
2007             }
2008             mShuffleMode = shufflemode;
2009             if (mShuffleMode == SHUFFLE_AUTO) {
2010                 if (makeAutoShuffleList()) {
2011                     mPlayListLen = 0;
2012                     doAutoShuffleUpdate();
2013                     mPlayPos = 0;
2014                     openCurrentAndNext();
2015                     play();
2016                     notifyChange(META_CHANGED);
2017                     return;
2018                 } else {
2019                     mShuffleMode = SHUFFLE_NONE;
2020                 }
2021             }
2022             saveQueue(false);
2023             notifyChange(SHUFFLEMODE_CHANGED);
2024         }
2025     }
2026
2027     /**
2028      * Sets the position of a track in the queue
2029      *
2030      * @param index The position to place the track
2031      */
2032     public void setQueuePosition(final int index) {
2033         synchronized (this) {
2034             stop(false);
2035             mPlayPos = index;
2036             openCurrentAndNext();
2037             play();
2038             notifyChange(META_CHANGED);
2039             if (mShuffleMode == SHUFFLE_AUTO) {
2040                 doAutoShuffleUpdate();
2041             }
2042         }
2043     }
2044
2045     /**
2046      * Queues a new list for playback
2047      *
2048      * @param list The list to queue
2049      * @param action The action to take
2050      */
2051     public void enqueue(final long[] list, final int action) {
2052         synchronized (this) {
2053             if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
2054                 addToPlayList(list, mPlayPos + 1);
2055                 notifyChange(QUEUE_CHANGED);
2056             } else {
2057                 addToPlayList(list, Integer.MAX_VALUE);
2058                 notifyChange(QUEUE_CHANGED);
2059                 if (action == NOW) {
2060                     mPlayPos = mPlayListLen - list.length;
2061                     openCurrentAndNext();
2062                     play();
2063                     notifyChange(META_CHANGED);
2064                     return;
2065                 }
2066             }
2067             if (mPlayPos < 0) {
2068                 mPlayPos = 0;
2069                 openCurrentAndNext();
2070                 play();
2071                 notifyChange(META_CHANGED);
2072             }
2073         }
2074     }
2075
2076     /**
2077      * Cycles through the different repeat modes
2078      */
2079     private void cycleRepeat() {
2080         if (mRepeatMode == REPEAT_NONE) {
2081             setRepeatMode(REPEAT_ALL);
2082         } else if (mRepeatMode == REPEAT_ALL) {
2083             setRepeatMode(REPEAT_CURRENT);
2084             if (mShuffleMode != SHUFFLE_NONE) {
2085                 setShuffleMode(SHUFFLE_NONE);
2086             }
2087         } else {
2088             setRepeatMode(REPEAT_NONE);
2089         }
2090     }
2091
2092     /**
2093      * Cycles through the different shuffle modes
2094      */
2095     private void cycleShuffle() {
2096         if (mShuffleMode == SHUFFLE_NONE) {
2097             setShuffleMode(SHUFFLE_NORMAL);
2098             if (mRepeatMode == REPEAT_CURRENT) {
2099                 setRepeatMode(REPEAT_ALL);
2100             }
2101         } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
2102             setShuffleMode(SHUFFLE_NONE);
2103         }
2104     }
2105
2106     /**
2107      * @return The album art for the current album.
2108      */
2109     public Bitmap getAlbumArt() {
2110         // Return the cached artwork
2111         final Bitmap bitmap = mImageFetcher.getArtwork(getAlbumName(),
2112                 getAlbumId(), getArtistName());
2113         return bitmap;
2114     }
2115
2116     /**
2117      * Called when one of the lists should refresh or requery.
2118      */
2119     public void refresh() {
2120         notifyChange(REFRESH);
2121     }
2122
2123     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2124         /**
2125          * {@inheritDoc}
2126          */
2127         @Override
2128         public void onReceive(final Context context, final Intent intent) {
2129             final String command = intent.getStringExtra(CMDNAME);
2130
2131             if (AppWidgetSmall.CMDAPPWIDGETUPDATE.equals(command)) {
2132                 final int[] small = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2133                 mAppWidgetSmall.performUpdate(MusicPlaybackService.this, small);
2134             } else if (AppWidgetLarge.CMDAPPWIDGETUPDATE.equals(command)) {
2135                 final int[] large = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2136                 mAppWidgetLarge.performUpdate(MusicPlaybackService.this, large);
2137             } else if (AppWidgetLargeAlternate.CMDAPPWIDGETUPDATE.equals(command)) {
2138                 final int[] largeAlt = intent
2139                         .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2140                 mAppWidgetLargeAlternate.performUpdate(MusicPlaybackService.this, largeAlt);
2141             } else if (RecentWidgetProvider.CMDAPPWIDGETUPDATE.equals(command)) {
2142                 final int[] recent = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2143                 mRecentWidgetProvider.performUpdate(MusicPlaybackService.this, recent);
2144             } else {
2145                 handleCommandIntent(intent);
2146             }
2147         }
2148     };
2149
2150     private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
2151         /**
2152          * {@inheritDoc}
2153          */
2154         @Override
2155         public void onAudioFocusChange(final int focusChange) {
2156             mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
2157         }
2158     };
2159
2160     private static final class DelayedHandler extends Handler {
2161         private final WeakReference<MusicPlaybackService> mService;
2162
2163         /**
2164          * Constructor of <code>DelayedHandler</code>
2165          *
2166          * @param service The service to use.
2167          */
2168         public DelayedHandler(final MusicPlaybackService service) {
2169             mService = new WeakReference<MusicPlaybackService>(service);
2170         }
2171
2172         /**
2173          * {@inheritDoc}
2174          */
2175         @Override
2176         public void handleMessage(final Message msg) {
2177             final MusicPlaybackService service = mService.get();
2178             if (service == null) {
2179                 return;
2180             }
2181             if (service.isPlaying()
2182                     || service.mPausedByTransientLossOfFocus
2183                     || service.mServiceInUse
2184                     || service.mPlayerHandler.hasMessages(TRACK_ENDED)) {
2185                 return;
2186             }
2187             service.saveQueue(true);
2188             service.stopSelf(service.mServiceStartId);
2189         }
2190     }
2191
2192     private static final class MusicPlayerHandler extends Handler {
2193         private final WeakReference<MusicPlaybackService> mService;
2194         private float mCurrentVolume = 1.0f;
2195
2196         /**
2197          * Constructor of <code>MusicPlayerHandler</code>
2198          *
2199          * @param service The service to use.
2200          * @param looper The thread to run on.
2201          */
2202         public MusicPlayerHandler(final MusicPlaybackService service, final Looper looper) {
2203             super(looper);
2204             mService = new WeakReference<MusicPlaybackService>(service);
2205         }
2206
2207         /**
2208          * {@inheritDoc}
2209          */
2210         @Override
2211         public void handleMessage(final Message msg) {
2212             final MusicPlaybackService service = mService.get();
2213             if (service == null) {
2214                 return;
2215             }
2216
2217             switch (msg.what) {
2218                 case FADEDOWN:
2219                     mCurrentVolume -= .05f;
2220                     if (mCurrentVolume > .2f) {
2221                         sendEmptyMessageDelayed(FADEDOWN, 10);
2222                     } else {
2223                         mCurrentVolume = .2f;
2224                     }
2225                     service.mPlayer.setVolume(mCurrentVolume);
2226                     break;
2227                 case FADEUP:
2228                     mCurrentVolume += .01f;
2229                     if (mCurrentVolume < 1.0f) {
2230                         sendEmptyMessageDelayed(FADEUP, 10);
2231                     } else {
2232                         mCurrentVolume = 1.0f;
2233                     }
2234                     service.mPlayer.setVolume(mCurrentVolume);
2235                     break;
2236                 case SERVER_DIED:
2237                     if (service.isPlaying()) {
2238                         service.gotoNext(true);
2239                     } else {
2240                         service.openCurrentAndNext();
2241                     }
2242                     break;
2243                 case TRACK_WENT_TO_NEXT:
2244                     service.mPlayPos = service.mNextPlayPos;
2245                     if (service.mCursor != null) {
2246                         service.mCursor.close();
2247                     }
2248                     service.mCursor = service.getCursorForId(service.mPlayList[service.mPlayPos]);
2249                     service.notifyChange(META_CHANGED);
2250                     service.updateNotification();
2251                     service.setNextTrack();
2252                     break;
2253                 case TRACK_ENDED:
2254                     if (service.mRepeatMode == REPEAT_CURRENT) {
2255                         service.seek(0);
2256                         service.play();
2257                     } else {
2258                         service.gotoNext(false);
2259                     }
2260                     break;
2261                 case RELEASE_WAKELOCK:
2262                     service.mWakeLock.release();
2263                     break;
2264                 case FOCUSCHANGE:
2265                     switch (msg.arg1) {
2266                         case AudioManager.AUDIOFOCUS_LOSS:
2267                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
2268                             if (service.isPlaying()) {
2269                                 service.mPausedByTransientLossOfFocus =
2270                                     msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
2271                             }
2272                             service.pause();
2273                             break;
2274                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
2275                             removeMessages(FADEUP);
2276                             sendEmptyMessage(FADEDOWN);
2277                             break;
2278                         case AudioManager.AUDIOFOCUS_GAIN:
2279                             if (!service.isPlaying()
2280                                     && service.mPausedByTransientLossOfFocus) {
2281                                 service.mPausedByTransientLossOfFocus = false;
2282                                 mCurrentVolume = 0f;
2283                                 service.mPlayer.setVolume(mCurrentVolume);
2284                                 service.play();
2285                             } else {
2286                                 removeMessages(FADEDOWN);
2287                                 sendEmptyMessage(FADEUP);
2288                             }
2289                             break;
2290                         default:
2291                     }
2292                     break;
2293                 default:
2294                     break;
2295             }
2296         }
2297     }
2298
2299     private static final class Shuffler {
2300
2301         private final LinkedList<Integer> mHistoryOfNumbers = new LinkedList<Integer>();
2302
2303         private final TreeSet<Integer> mPreviousNumbers = new TreeSet<Integer>();
2304
2305         private final Random mRandom = new Random();
2306
2307         private int mPrevious;
2308
2309         /**
2310          * Constructor of <code>Shuffler</code>
2311          */
2312         public Shuffler() {
2313             super();
2314         }
2315
2316         /**
2317          * @param interval The length the queue
2318          * @return The position of the next track to play
2319          */
2320         public int nextInt(final int interval) {
2321             int next;
2322             do {
2323                 next = mRandom.nextInt(interval);
2324             } while (next == mPrevious && interval > 1
2325                     && !mPreviousNumbers.contains(Integer.valueOf(next)));
2326             mPrevious = next;
2327             mHistoryOfNumbers.add(mPrevious);
2328             mPreviousNumbers.add(mPrevious);
2329             cleanUpHistory();
2330             return next;
2331         }
2332
2333         /**
2334          * Removes old tracks and cleans up the history preparing for new tracks
2335          * to be added to the mapping
2336          */
2337         private void cleanUpHistory() {
2338             if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {
2339                 for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {
2340                     mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());
2341                 }
2342             }
2343         }
2344     };
2345
2346     private static final class MultiPlayer implements MediaPlayer.OnErrorListener,
2347             MediaPlayer.OnCompletionListener {
2348
2349         private final WeakReference<MusicPlaybackService> mService;
2350
2351         private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
2352
2353         private MediaPlayer mNextMediaPlayer;
2354
2355         private Handler mHandler;
2356
2357         private boolean mIsInitialized = false;
2358
2359         /**
2360          * Constructor of <code>MultiPlayer</code>
2361          */
2362         public MultiPlayer(final MusicPlaybackService service) {
2363             mService = new WeakReference<MusicPlaybackService>(service);
2364             mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2365         }
2366
2367         /**
2368          * @param path The path of the file, or the http/rtsp URL of the stream
2369          *            you want to play
2370          */
2371         public void setDataSource(final String path) {
2372             mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
2373             if (mIsInitialized) {
2374                 setNextDataSource(null);
2375             }
2376         }
2377
2378         /**
2379          * @param player The {@link MediaPlayer} to use
2380          * @param path The path of the file, or the http/rtsp URL of the stream
2381          *            you want to play
2382          * @return True if the <code>player</code> has been prepared and is
2383          *         ready to play, false otherwise
2384          */
2385         private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
2386             try {
2387                 player.reset();
2388                 player.setOnPreparedListener(null);
2389                 if (path.startsWith("content://")) {
2390                     player.setDataSource(mService.get(), Uri.parse(path));
2391                 } else {
2392                     player.setDataSource(path);
2393                 }
2394                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
2395                 player.prepare();
2396             } catch (final IOException todo) {
2397                 // TODO: notify the user why the file couldn't be opened
2398                 return false;
2399             } catch (final IllegalArgumentException todo) {
2400                 // TODO: notify the user why the file couldn't be opened
2401                 return false;
2402             }
2403             player.setOnCompletionListener(this);
2404             player.setOnErrorListener(this);
2405             final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2406             intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2407             intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, mService.get().getPackageName());
2408             mService.get().sendBroadcast(intent);
2409             return true;
2410         }
2411
2412         /**
2413          * Set the MediaPlayer to start when this MediaPlayer finishes playback.
2414          *
2415          * @param path The path of the file, or the http/rtsp URL of the stream
2416          *            you want to play
2417          */
2418         public void setNextDataSource(final String path) {
2419             mCurrentMediaPlayer.setNextMediaPlayer(null);
2420             if (mNextMediaPlayer != null) {
2421                 mNextMediaPlayer.release();
2422                 mNextMediaPlayer = null;
2423             }
2424             if (path == null) {
2425                 return;
2426             }
2427             mNextMediaPlayer = new MediaPlayer();
2428             mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2429             mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
2430             if (setDataSourceImpl(mNextMediaPlayer, path)) {
2431                 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
2432             } else {
2433                 if (mNextMediaPlayer != null) {
2434                     mNextMediaPlayer.release();
2435                     mNextMediaPlayer = null;
2436                 }
2437             }
2438         }
2439
2440         /**
2441          * Sets the handler
2442          *
2443          * @param handler The handler to use
2444          */
2445         public void setHandler(final Handler handler) {
2446             mHandler = handler;
2447         }
2448
2449         /**
2450          * @return True if the player is ready to go, false otherwise
2451          */
2452         public boolean isInitialized() {
2453             return mIsInitialized;
2454         }
2455
2456         /**
2457          * Starts or resumes playback.
2458          */
2459         public void start() {
2460             mCurrentMediaPlayer.start();
2461         }
2462
2463         /**
2464          * Resets the MediaPlayer to its uninitialized state.
2465          */
2466         public void stop() {
2467             mCurrentMediaPlayer.reset();
2468             mIsInitialized = false;
2469         }
2470
2471         /**
2472          * Releases resources associated with this MediaPlayer object.
2473          */
2474         public void release() {
2475             stop();
2476             mCurrentMediaPlayer.release();
2477         }
2478
2479         /**
2480          * Pauses playback. Call start() to resume.
2481          */
2482         public void pause() {
2483             mCurrentMediaPlayer.pause();
2484         }
2485
2486         /**
2487          * Gets the duration of the file.
2488          *
2489          * @return The duration in milliseconds
2490          */
2491         public long duration() {
2492             return mCurrentMediaPlayer.getDuration();
2493         }
2494
2495         /**
2496          * Gets the current playback position.
2497          *
2498          * @return The current position in milliseconds
2499          */
2500         public long position() {
2501             return mCurrentMediaPlayer.getCurrentPosition();
2502         }
2503
2504         /**
2505          * Gets the current playback position.
2506          *
2507          * @param whereto The offset in milliseconds from the start to seek to
2508          * @return The offset in milliseconds from the start to seek to
2509          */
2510         public long seek(final long whereto) {
2511             mCurrentMediaPlayer.seekTo((int)whereto);
2512             return whereto;
2513         }
2514
2515         /**
2516          * Sets the volume on this player.
2517          *
2518          * @param vol Left and right volume scalar
2519          */
2520         public void setVolume(final float vol) {
2521             mCurrentMediaPlayer.setVolume(vol, vol);
2522         }
2523
2524         /**
2525          * Sets the audio session ID.
2526          *
2527          * @param sessionId The audio session ID
2528          */
2529         public void setAudioSessionId(final int sessionId) {
2530             mCurrentMediaPlayer.setAudioSessionId(sessionId);
2531         }
2532
2533         /**
2534          * Returns the audio session ID.
2535          *
2536          * @return The current audio session ID.
2537          */
2538         public int getAudioSessionId() {
2539             return mCurrentMediaPlayer.getAudioSessionId();
2540         }
2541
2542         /**
2543          * {@inheritDoc}
2544          */
2545         @Override
2546         public boolean onError(final MediaPlayer mp, final int what, final int extra) {
2547             switch (what) {
2548                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
2549                     mIsInitialized = false;
2550                     mCurrentMediaPlayer.release();
2551                     mCurrentMediaPlayer = new MediaPlayer();
2552                     mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2553                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
2554                     return true;
2555                 default:
2556                     break;
2557             }
2558             return false;
2559         }
2560
2561         /**
2562          * {@inheritDoc}
2563          */
2564         @Override
2565         public void onCompletion(final MediaPlayer mp) {
2566             if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
2567                 mCurrentMediaPlayer.release();
2568                 mCurrentMediaPlayer = mNextMediaPlayer;
2569                 mNextMediaPlayer = null;
2570                 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
2571             } else {
2572                 mService.get().mWakeLock.acquire(30000);
2573                 mHandler.sendEmptyMessage(TRACK_ENDED);
2574                 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
2575             }
2576         }
2577     }
2578
2579     private static final class ServiceStub extends IApolloService.Stub {
2580
2581         private final WeakReference<MusicPlaybackService> mService;
2582
2583         private ServiceStub(final MusicPlaybackService service) {
2584             mService = new WeakReference<MusicPlaybackService>(service);
2585         }
2586
2587         /**
2588          * {@inheritDoc}
2589          */
2590         @Override
2591         public void openFile(final String path) throws RemoteException {
2592             mService.get().openFile(path);
2593         }
2594
2595         /**
2596          * {@inheritDoc}
2597          */
2598         @Override
2599         public void open(final long[] list, final int position) throws RemoteException {
2600             mService.get().open(list, position);
2601         }
2602
2603         /**
2604          * {@inheritDoc}
2605          */
2606         @Override
2607         public void stop() throws RemoteException {
2608             mService.get().stop();
2609         }
2610
2611         /**
2612          * {@inheritDoc}
2613          */
2614         @Override
2615         public void pause() throws RemoteException {
2616             mService.get().pause();
2617         }
2618
2619         /**
2620          * {@inheritDoc}
2621          */
2622         @Override
2623         public void play() throws RemoteException {
2624             mService.get().play();
2625         }
2626
2627         /**
2628          * {@inheritDoc}
2629          */
2630         @Override
2631         public void prev() throws RemoteException {
2632             mService.get().prev();
2633         }
2634
2635         /**
2636          * {@inheritDoc}
2637          */
2638         @Override
2639         public void next() throws RemoteException {
2640             mService.get().gotoNext(true);
2641         }
2642
2643         /**
2644          * {@inheritDoc}
2645          */
2646         @Override
2647         public void enqueue(final long[] list, final int action) throws RemoteException {
2648             mService.get().enqueue(list, action);
2649         }
2650
2651         /**
2652          * {@inheritDoc}
2653          */
2654         @Override
2655         public void setQueuePosition(final int index) throws RemoteException {
2656             mService.get().setQueuePosition(index);
2657         }
2658
2659         /**
2660          * {@inheritDoc}
2661          */
2662         @Override
2663         public void setShuffleMode(final int shufflemode) throws RemoteException {
2664             mService.get().setShuffleMode(shufflemode);
2665         }
2666
2667         /**
2668          * {@inheritDoc}
2669          */
2670         @Override
2671         public void setRepeatMode(final int repeatmode) throws RemoteException {
2672             mService.get().setRepeatMode(repeatmode);
2673         }
2674
2675         /**
2676          * {@inheritDoc}
2677          */
2678         @Override
2679         public void moveQueueItem(final int from, final int to) throws RemoteException {
2680             mService.get().moveQueueItem(from, to);
2681         }
2682
2683         /**
2684          * {@inheritDoc}
2685          */
2686         @Override
2687         public void toggleFavorite() throws RemoteException {
2688             mService.get().toggleFavorite();
2689         }
2690
2691         /**
2692          * {@inheritDoc}
2693          */
2694         @Override
2695         public void refresh() throws RemoteException {
2696             mService.get().refresh();
2697         }
2698
2699         /**
2700          * {@inheritDoc}
2701          */
2702         @Override
2703         public boolean isFavorite() throws RemoteException {
2704             return mService.get().isFavorite();
2705         }
2706
2707         /**
2708          * {@inheritDoc}
2709          */
2710         @Override
2711         public boolean isPlaying() throws RemoteException {
2712             return mService.get().isPlaying();
2713         }
2714
2715         /**
2716          * {@inheritDoc}
2717          */
2718         @Override
2719         public long[] getQueue() throws RemoteException {
2720             return mService.get().getQueue();
2721         }
2722
2723         /**
2724          * {@inheritDoc}
2725          */
2726         @Override
2727         public long duration() throws RemoteException {
2728             return mService.get().duration();
2729         }
2730
2731         /**
2732          * {@inheritDoc}
2733          */
2734         @Override
2735         public long position() throws RemoteException {
2736             return mService.get().position();
2737         }
2738
2739         /**
2740          * {@inheritDoc}
2741          */
2742         @Override
2743         public long seek(final long position) throws RemoteException {
2744             return mService.get().seek(position);
2745         }
2746
2747         /**
2748          * {@inheritDoc}
2749          */
2750         @Override
2751         public long getAudioId() throws RemoteException {
2752             return mService.get().getAudioId();
2753         }
2754
2755         /**
2756          * {@inheritDoc}
2757          */
2758         @Override
2759         public long getArtistId() throws RemoteException {
2760             return mService.get().getArtistId();
2761         }
2762
2763         /**
2764          * {@inheritDoc}
2765          */
2766         @Override
2767         public long getAlbumId() throws RemoteException {
2768             return mService.get().getAlbumId();
2769         }
2770
2771         /**
2772          * {@inheritDoc}
2773          */
2774         @Override
2775         public String getArtistName() throws RemoteException {
2776             return mService.get().getArtistName();
2777         }
2778
2779         /**
2780          * {@inheritDoc}
2781          */
2782         @Override
2783         public String getTrackName() throws RemoteException {
2784             return mService.get().getTrackName();
2785         }
2786
2787         /**
2788          * {@inheritDoc}
2789          */
2790         @Override
2791         public String getAlbumName() throws RemoteException {
2792             return mService.get().getAlbumName();
2793         }
2794
2795         /**
2796          * {@inheritDoc}
2797          */
2798         @Override
2799         public String getPath() throws RemoteException {
2800             return mService.get().getPath();
2801         }
2802
2803         /**
2804          * {@inheritDoc}
2805          */
2806         @Override
2807         public int getQueuePosition() throws RemoteException {
2808             return mService.get().getQueuePosition();
2809         }
2810
2811         /**
2812          * {@inheritDoc}
2813          */
2814         @Override
2815         public int getShuffleMode() throws RemoteException {
2816             return mService.get().getShuffleMode();
2817         }
2818
2819         /**
2820          * {@inheritDoc}
2821          */
2822         @Override
2823         public int getRepeatMode() throws RemoteException {
2824             return mService.get().getRepeatMode();
2825         }
2826
2827         /**
2828          * {@inheritDoc}
2829          */
2830         @Override
2831         public int removeTracks(final int first, final int last) throws RemoteException {
2832             return mService.get().removeTracks(first, last);
2833         }
2834
2835         /**
2836          * {@inheritDoc}
2837          */
2838         @Override
2839         public int removeTrack(final long id) throws RemoteException {
2840             return mService.get().removeTrack(id);
2841         }
2842
2843         /**
2844          * {@inheritDoc}
2845          */
2846         @Override
2847         public int getMediaMountedCount() throws RemoteException {
2848             return mService.get().getMediaMountedCount();
2849         }
2850
2851         /**
2852          * {@inheritDoc}
2853          */
2854         @Override
2855         public int getAudioSessionId() throws RemoteException {
2856             return mService.get().getAudioSessionId();
2857         }
2858
2859     }
2860
2861 }