OSDN Git Service

72fc28e496bd29bc62786562d0d9b81c2f97a6ad
[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 <<<<<<< HEAD
667             handleCommandIntent(intent);
668 =======
669             if (UPDATE_LOCKSCREEN.equals(action)) {
670                 mEnableLockscreenControls = intent.getBooleanExtra(UPDATE_LOCKSCREEN, true);
671                 if (mEnableLockscreenControls) {
672                     setUpRemoteControlClient();
673                     // Update the controls according to the current playback
674                     notifyChange(PLAYSTATE_CHANGED);
675                     notifyChange(META_CHANGED);
676                 } else {
677                     // Remove then unregister the controls
678                     mRemoteControlClient
679                             .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
680                     mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
681                 }
682             } else if (KILL_NOTIFICATION.equals(action)) {
683                 mNotificationHelper.killNotification();
684             } else {
685                 handleCommandIntent(intent);
686             }
687 >>>>>>> aeb807a... Make sure notification is shown when playback is started via media
688         }
689
690         // Make sure the service will shut down on its own if it was
691         // just started but not bound to and nothing is playing
692         mDelayedStopHandler.removeCallbacksAndMessages(null);
693         final Message msg = mDelayedStopHandler.obtainMessage();
694         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
695         return START_STICKY;
696     }
697
698     private void handleCommandIntent(Intent intent) {
699         final String action = intent.getAction();
700         final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
701
702         if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
703             gotoNext(true);
704         } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)) {
705             if (position() < 2000) {
706                 prev();
707             } else {
708                 seek(0);
709                 play();
710             }
711         } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
712             if (isPlaying()) {
713                 pause();
714                 mPausedByTransientLossOfFocus = false;
715             } else {
716                 play();
717             }
718         } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
719             pause();
720             mPausedByTransientLossOfFocus = false;
721         } else if (CMDPLAY.equals(command)) {
722             play();
723         } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
724             pause();
725             mPausedByTransientLossOfFocus = false;
726             seek(0);
727             mNotificationHelper.killNotification();
728         } else if (REPEAT_ACTION.equals(action)) {
729             cycleRepeat();
730         } else if (SHUFFLE_ACTION.equals(action)) {
731             cycleShuffle();
732         }
733     }
734
735     /**
736      * Updates the notification, considering the current play and activity state
737      */
738     private void updateNotification() {
739         if (!mAnyActivityInForeground && isPlaying()) {
740             mAlarmManager.cancel(mKillNotificationIntent);
741             mNotificationHelper.buildNotification(getAlbumName(), getArtistName(),
742                     getTrackName(), getAlbumId(), getAlbumArt(), isPlaying());
743         } else if (mAnyActivityInForeground) {
744             mNotificationHelper.killNotification();
745         }
746     }
747
748     /**
749      * @return A card ID used to save and restore playlists, i.e., the queue.
750      */
751     private int getCardId() {
752         final ContentResolver resolver = getContentResolver();
753         Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
754                 null, null);
755         int mCardId = -1;
756         if (cursor != null && cursor.moveToFirst()) {
757             mCardId = cursor.getInt(0);
758             cursor.close();
759             cursor = null;
760         }
761         return mCardId;
762     }
763
764     /**
765      * Called when we receive a ACTION_MEDIA_EJECT notification.
766      *
767      * @param storagePath The path to mount point for the removed media
768      */
769     public void closeExternalStorageFiles(final String storagePath) {
770         stop(true);
771         notifyChange(QUEUE_CHANGED);
772         notifyChange(META_CHANGED);
773     }
774
775     /**
776      * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
777      * intent will call closeExternalStorageFiles() if the external media is
778      * going to be ejected, so applications can clean up any files they have
779      * open.
780      */
781     public void registerExternalStorageListener() {
782         if (mUnmountReceiver == null) {
783             mUnmountReceiver = new BroadcastReceiver() {
784
785                 /**
786                  * {@inheritDoc}
787                  */
788                 @Override
789                 public void onReceive(final Context context, final Intent intent) {
790                     final String action = intent.getAction();
791                     if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
792                         saveQueue(true);
793                         mQueueIsSaveable = false;
794                         closeExternalStorageFiles(intent.getData().getPath());
795                     } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
796                         mMediaMountedCount++;
797                         mCardId = getCardId();
798                         reloadQueue();
799                         mQueueIsSaveable = true;
800                         notifyChange(QUEUE_CHANGED);
801                         notifyChange(META_CHANGED);
802                     }
803                 }
804             };
805             final IntentFilter filter = new IntentFilter();
806             filter.addAction(Intent.ACTION_MEDIA_EJECT);
807             filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
808             filter.addDataScheme("file");
809             registerReceiver(mUnmountReceiver, filter);
810         }
811     }
812
813     /**
814      * Changes the notification buttons to a paused state and beging the
815      * countdown to calling {@code #stopForeground(true)}
816      */
817     private void gotoIdleState() {
818         mDelayedStopHandler.removeCallbacksAndMessages(null);
819         final Message msg = mDelayedStopHandler.obtainMessage();
820         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
821         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
822                 SystemClock.elapsedRealtime() + IDLE_DELAY, mKillNotificationIntent);
823     }
824
825     /**
826      * Stops playback
827      *
828      * @param goToIdle True to go to the idle state, false otherwise
829      */
830     private void stop(final boolean goToIdle) {
831         if (mPlayer.isInitialized()) {
832             mPlayer.stop();
833         }
834         mFileToPlay = null;
835         if (mCursor != null) {
836             mCursor.close();
837             mCursor = null;
838         }
839         if (goToIdle) {
840             gotoIdleState();
841             mIsSupposedToBePlaying = false;
842         } else {
843             stopForeground(false);
844         }
845     }
846
847     /**
848      * Removes the range of tracks specified from the play list. If a file
849      * within the range is the file currently being played, playback will move
850      * to the next file after the range.
851      *
852      * @param first The first file to be removed
853      * @param last The last file to be removed
854      * @return the number of tracks deleted
855      */
856     private int removeTracksInternal(int first, int last) {
857         synchronized (this) {
858             if (last < first) {
859                 return 0;
860             } else if (first < 0) {
861                 first = 0;
862             } else if (last >= mPlayListLen) {
863                 last = mPlayListLen - 1;
864             }
865
866             boolean gotonext = false;
867             if (first <= mPlayPos && mPlayPos <= last) {
868                 mPlayPos = first;
869                 gotonext = true;
870             } else if (mPlayPos > last) {
871                 mPlayPos -= last - first + 1;
872             }
873             final int num = mPlayListLen - last - 1;
874             for (int i = 0; i < num; i++) {
875                 mPlayList[first + i] = mPlayList[last + 1 + i];
876             }
877             mPlayListLen -= last - first + 1;
878
879             if (gotonext) {
880                 if (mPlayListLen == 0) {
881                     stop(true);
882                     mPlayPos = -1;
883                     if (mCursor != null) {
884                         mCursor.close();
885                         mCursor = null;
886                     }
887                 } else {
888                     if (mPlayPos >= mPlayListLen) {
889                         mPlayPos = 0;
890                     }
891                     final boolean wasPlaying = isPlaying();
892                     stop(false);
893                     openCurrentAndNext();
894                     if (wasPlaying) {
895                         play();
896                     }
897                 }
898                 notifyChange(META_CHANGED);
899             }
900             return last - first + 1;
901         }
902     }
903
904     /**
905      * Adds a list to the playlist
906      *
907      * @param list The list to add
908      * @param position The position to place the tracks
909      */
910     private void addToPlayList(final long[] list, int position) {
911         final int addlen = list.length;
912         if (position < 0) {
913             mPlayListLen = 0;
914             position = 0;
915         }
916         ensurePlayListCapacity(mPlayListLen + addlen);
917         if (position > mPlayListLen) {
918             position = mPlayListLen;
919         }
920
921         final int tailsize = mPlayListLen - position;
922         for (int i = tailsize; i > 0; i--) {
923             mPlayList[position + i] = mPlayList[position + i - addlen];
924         }
925
926         for (int i = 0; i < addlen; i++) {
927             mPlayList[position + i] = list[i];
928         }
929         mPlayListLen += addlen;
930         if (mPlayListLen == 0) {
931             mCursor.close();
932             mCursor = null;
933             notifyChange(META_CHANGED);
934         }
935     }
936
937     /**
938      * @param lid The list ID
939      * @return The cursor used for a specific ID
940      */
941     private Cursor getCursorForId(final long id) {
942         final Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
943                 PROJECTION, "_id=" + id, null, null);
944         if (c != null) {
945             c.moveToFirst();
946         }
947         return c;
948     }
949
950     /**
951      * Called to open a new file as the current track and prepare the next for
952      * playback
953      */
954     private void openCurrentAndNext() {
955         openCurrentAndMaybeNext(true);
956     }
957
958     /**
959      * Called to open a new file as the current track and prepare the next for
960      * playback
961      *
962      * @param openNext True to prepare the next track for playback, false
963      *            otherwise.
964      */
965     private void openCurrentAndMaybeNext(final boolean openNext) {
966         synchronized (this) {
967             if (mCursor != null) {
968                 mCursor.close();
969                 mCursor = null;
970             }
971
972             if (mPlayListLen == 0) {
973                 return;
974             }
975             stop(false);
976
977             mCursor = getCursorForId(mPlayList[mPlayPos]);
978             while (true) {
979                 if (mCursor != null
980                         && mCursor.getCount() != 0
981                         && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
982                                 + mCursor.getLong(IDCOLIDX))) {
983                     break;
984                 }
985                 // if we get here then opening the file failed. We can close the
986                 // cursor now, because
987                 // we're either going to create a new one next, or stop trying
988                 if (mCursor != null) {
989                     mCursor.close();
990                     mCursor = null;
991                 }
992                 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
993                     final int pos = getNextPosition(false);
994                     if (pos < 0) {
995                         gotoIdleState();
996                         if (mIsSupposedToBePlaying) {
997                             mIsSupposedToBePlaying = false;
998                             notifyChange(PLAYSTATE_CHANGED);
999                         }
1000                         return;
1001                     }
1002                     mPlayPos = pos;
1003                     stop(false);
1004                     mPlayPos = pos;
1005                     mCursor = getCursorForId(mPlayList[mPlayPos]);
1006                 } else {
1007                     mOpenFailedCounter = 0;
1008                     gotoIdleState();
1009                     if (mIsSupposedToBePlaying) {
1010                         mIsSupposedToBePlaying = false;
1011                         notifyChange(PLAYSTATE_CHANGED);
1012                     }
1013                     return;
1014                 }
1015             }
1016             if (openNext) {
1017                 setNextTrack();
1018             }
1019         }
1020     }
1021
1022     /**
1023      * @param force True to force the player onto the track next, false
1024      *            otherwise.
1025      * @return The next position to play.
1026      */
1027     private int getNextPosition(final boolean force) {
1028         if (!force && mRepeatMode == REPEAT_CURRENT) {
1029             if (mPlayPos < 0) {
1030                 return 0;
1031             }
1032             return mPlayPos;
1033         } else if (mShuffleMode == SHUFFLE_NORMAL) {
1034             if (mPlayPos >= 0) {
1035                 mHistory.add(mPlayPos);
1036             }
1037             if (mHistory.size() > MAX_HISTORY_SIZE) {
1038                 mHistory.remove(0);
1039             }
1040             final int numTracks = mPlayListLen;
1041             final int[] tracks = new int[numTracks];
1042             for (int i = 0; i < numTracks; i++) {
1043                 tracks[i] = i;
1044             }
1045
1046             final int numHistory = mHistory.size();
1047             int numUnplayed = numTracks;
1048             for (int i = 0; i < numHistory; i++) {
1049                 final int idx = mHistory.get(i).intValue();
1050                 if (idx < numTracks && tracks[idx] >= 0) {
1051                     numUnplayed--;
1052                     tracks[idx] = -1;
1053                 }
1054             }
1055             if (numUnplayed <= 0) {
1056                 if (mRepeatMode == REPEAT_ALL || force) {
1057                     numUnplayed = numTracks;
1058                     for (int i = 0; i < numTracks; i++) {
1059                         tracks[i] = i;
1060                     }
1061                 } else {
1062                     return -1;
1063                 }
1064             }
1065             int skip = 0;
1066             if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
1067                 skip = mShuffler.nextInt(numUnplayed);
1068             }
1069             int cnt = -1;
1070             while (true) {
1071                 while (tracks[++cnt] < 0) {
1072                     ;
1073                 }
1074                 skip--;
1075                 if (skip < 0) {
1076                     break;
1077                 }
1078             }
1079             return cnt;
1080         } else if (mShuffleMode == SHUFFLE_AUTO) {
1081             doAutoShuffleUpdate();
1082             return mPlayPos + 1;
1083         } else {
1084             if (mPlayPos >= mPlayListLen - 1) {
1085                 if (mRepeatMode == REPEAT_NONE && !force) {
1086                     return -1;
1087                 } else if (mRepeatMode == REPEAT_ALL || force) {
1088                     return 0;
1089                 }
1090                 return -1;
1091             } else {
1092                 return mPlayPos + 1;
1093             }
1094         }
1095     }
1096
1097     /**
1098      * Sets the track track to be played
1099      */
1100     private void setNextTrack() {
1101         mNextPlayPos = getNextPosition(false);
1102         if (mNextPlayPos >= 0 && mPlayList != null) {
1103             final long id = mPlayList[mNextPlayPos];
1104             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1105         }
1106     }
1107
1108     /**
1109      * Creates a shuffled playlist used for party mode
1110      */
1111     private boolean makeAutoShuffleList() {
1112         Cursor cursor = null;
1113         try {
1114             cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1115                     new String[] {
1116                         MediaStore.Audio.Media._ID
1117                     }, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
1118             if (cursor == null || cursor.getCount() == 0) {
1119                 return false;
1120             }
1121             final int len = cursor.getCount();
1122             final long[] list = new long[len];
1123             for (int i = 0; i < len; i++) {
1124                 cursor.moveToNext();
1125                 list[i] = cursor.getLong(0);
1126             }
1127             mAutoShuffleList = list;
1128             return true;
1129         } catch (final RuntimeException e) {
1130         } finally {
1131             if (cursor != null) {
1132                 cursor.close();
1133                 cursor = null;
1134             }
1135         }
1136         return false;
1137     }
1138
1139     /**
1140      * Creates the party shuffle playlist
1141      */
1142     private void doAutoShuffleUpdate() {
1143         boolean notify = false;
1144         if (mPlayPos > 10) {
1145             removeTracks(0, mPlayPos - 9);
1146             notify = true;
1147         }
1148         final int toAdd = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1149         for (int i = 0; i < toAdd; i++) {
1150             int lookback = mHistory.size();
1151             int idx = -1;
1152             while (true) {
1153                 idx = mShuffler.nextInt(mAutoShuffleList.length);
1154                 if (!wasRecentlyUsed(idx, lookback)) {
1155                     break;
1156                 }
1157                 lookback /= 2;
1158             }
1159             mHistory.add(idx);
1160             if (mHistory.size() > MAX_HISTORY_SIZE) {
1161                 mHistory.remove(0);
1162             }
1163             ensurePlayListCapacity(mPlayListLen + 1);
1164             mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
1165             notify = true;
1166         }
1167         if (notify) {
1168             notifyChange(QUEUE_CHANGED);
1169         }
1170     }
1171
1172     /**/
1173     private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
1174         if (lookbacksize == 0) {
1175             return false;
1176         }
1177         final int histsize = mHistory.size();
1178         if (histsize < lookbacksize) {
1179             lookbacksize = histsize;
1180         }
1181         final int maxidx = histsize - 1;
1182         for (int i = 0; i < lookbacksize; i++) {
1183             final long entry = mHistory.get(maxidx - i);
1184             if (entry == idx) {
1185                 return true;
1186             }
1187         }
1188         return false;
1189     }
1190
1191     /**
1192      * Makes sure the playlist has enough space to hold all of the songs
1193      *
1194      * @param size The size of the playlist
1195      */
1196     private void ensurePlayListCapacity(final int size) {
1197         if (mPlayList == null || size > mPlayList.length) {
1198             // reallocate at 2x requested size so we don't
1199             // need to grow and copy the array for every
1200             // insert
1201             final long[] newlist = new long[size * 2];
1202             final int len = mPlayList != null ? mPlayList.length : mPlayListLen;
1203             for (int i = 0; i < len; i++) {
1204                 newlist[i] = mPlayList[i];
1205             }
1206             mPlayList = newlist;
1207         }
1208         // FIXME: shrink the array when the needed size is much smaller
1209         // than the allocated size
1210     }
1211
1212     /**
1213      * Notify the change-receivers that something has changed.
1214      */
1215     private void notifyChange(final String what) {
1216         final Intent intent = new Intent(what);
1217         intent.putExtra("id", getAudioId());
1218         intent.putExtra("artist", getArtistName());
1219         intent.putExtra("album", getAlbumName());
1220         intent.putExtra("track", getTrackName());
1221         intent.putExtra("playing", isPlaying());
1222         intent.putExtra("isfavorite", isFavorite());
1223         sendStickyBroadcast(intent);
1224
1225         final Intent musicIntent = new Intent(intent);
1226         musicIntent.setAction(what.replace(APOLLO_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
1227         sendStickyBroadcast(musicIntent);
1228
1229         // Update the lockscreen controls
1230         updateRemoteControlClient(what);
1231
1232         if (what.equals(META_CHANGED)) {
1233             // Increase the play count for favorite songs.
1234             if (mFavoritesCache.getSongId(getAudioId()) != null) {
1235                 mFavoritesCache.addSongId(getAudioId(), getTrackName(), getAlbumName(),
1236                         getArtistName());
1237             }
1238             // Add the track to the recently played list.
1239             mRecentsCache.addAlbumId(getAlbumId(), getAlbumName(), getArtistName(),
1240                     MusicUtils.getSongCountForAlbum(this, getAlbumName()),
1241                     MusicUtils.getReleaseDateForAlbum(this, getAlbumName()));
1242         } else if (what.equals(QUEUE_CHANGED)) {
1243             saveQueue(true);
1244         } else {
1245             saveQueue(false);
1246         }
1247
1248         if (what.equals(PLAYSTATE_CHANGED)) {
1249             mNotificationHelper.updatePlayState(isPlaying());
1250         }
1251
1252         // Update the app-widgets
1253         mAppWidgetSmall.notifyChange(this, what);
1254         mAppWidgetLarge.notifyChange(this, what);
1255         mAppWidgetLargeAlternate.notifyChange(this, what);
1256         mRecentWidgetProvider.notifyChange(this, what);
1257     }
1258
1259     /**
1260      * Updates the lockscreen controls.
1261      *
1262      * @param what The broadcast
1263      */
1264     private void updateRemoteControlClient(final String what) {
1265         if (what.equals(PLAYSTATE_CHANGED)) {
1266             mRemoteControlClient.setPlaybackState(mIsSupposedToBePlaying
1267                     ? RemoteControlClient.PLAYSTATE_PLAYING
1268                     : RemoteControlClient.PLAYSTATE_PAUSED);
1269         } else if (what.equals(META_CHANGED)) {
1270             Bitmap albumArt = getAlbumArt();
1271             if (albumArt != null) {
1272                 // RemoteControlClient wants to recycle the bitmaps thrown at it, so we need
1273                 // to make sure not to hand out our cache copy
1274                 Bitmap.Config config = albumArt.getConfig();
1275                 if (config == null) {
1276                     config = Bitmap.Config.ARGB_8888;
1277                 }
1278                 albumArt = albumArt.copy(config, false);
1279             }
1280             mRemoteControlClient
1281                     .editMetadata(true)
1282                     .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName())
1283                     .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName())
1284                     .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName())
1285                     .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration())
1286                     .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt)
1287                     .apply();
1288         }
1289     }
1290
1291     /**
1292      * Saves the queue
1293      *
1294      * @param full True if the queue is full
1295      */
1296     private void saveQueue(final boolean full) {
1297         if (!mQueueIsSaveable) {
1298             return;
1299         }
1300
1301         final SharedPreferences.Editor editor = mPreferences.edit();
1302         if (full) {
1303             final StringBuilder q = new StringBuilder();
1304             int len = mPlayListLen;
1305             for (int i = 0; i < len; i++) {
1306                 long n = mPlayList[i];
1307                 if (n < 0) {
1308                     continue;
1309                 } else if (n == 0) {
1310                     q.append("0;");
1311                 } else {
1312                     while (n != 0) {
1313                         final int digit = (int)(n & 0xf);
1314                         n >>>= 4;
1315                         q.append(HEX_DIGITS[digit]);
1316                     }
1317                     q.append(";");
1318                 }
1319             }
1320             editor.putString("queue", q.toString());
1321             editor.putInt("cardid", mCardId);
1322             if (mShuffleMode != SHUFFLE_NONE) {
1323                 len = mHistory.size();
1324                 q.setLength(0);
1325                 for (int i = 0; i < len; i++) {
1326                     int n = mHistory.get(i);
1327                     if (n == 0) {
1328                         q.append("0;");
1329                     } else {
1330                         while (n != 0) {
1331                             final int digit = n & 0xf;
1332                             n >>>= 4;
1333                             q.append(HEX_DIGITS[digit]);
1334                         }
1335                         q.append(";");
1336                     }
1337                 }
1338                 editor.putString("history", q.toString());
1339             }
1340         }
1341         editor.putInt("curpos", mPlayPos);
1342         if (mPlayer.isInitialized()) {
1343             editor.putLong("seekpos", mPlayer.position());
1344         }
1345         editor.putInt("repeatmode", mRepeatMode);
1346         editor.putInt("shufflemode", mShuffleMode);
1347         editor.apply();
1348     }
1349
1350     /**
1351      * Reloads the queue as the user left it the last time they stopped using
1352      * Apollo
1353      */
1354     private void reloadQueue() {
1355         String q = null;
1356         int id = mCardId;
1357         if (mPreferences.contains("cardid")) {
1358             id = mPreferences.getInt("cardid", ~mCardId);
1359         }
1360         if (id == mCardId) {
1361             q = mPreferences.getString("queue", "");
1362         }
1363         int qlen = q != null ? q.length() : 0;
1364         if (qlen > 1) {
1365             int plen = 0;
1366             int n = 0;
1367             int shift = 0;
1368             for (int i = 0; i < qlen; i++) {
1369                 final char c = q.charAt(i);
1370                 if (c == ';') {
1371                     ensurePlayListCapacity(plen + 1);
1372                     mPlayList[plen] = n;
1373                     plen++;
1374                     n = 0;
1375                     shift = 0;
1376                 } else {
1377                     if (c >= '0' && c <= '9') {
1378                         n += c - '0' << shift;
1379                     } else if (c >= 'a' && c <= 'f') {
1380                         n += 10 + c - 'a' << shift;
1381                     } else {
1382                         plen = 0;
1383                         break;
1384                     }
1385                     shift += 4;
1386                 }
1387             }
1388             mPlayListLen = plen;
1389             final int pos = mPreferences.getInt("curpos", 0);
1390             if (pos < 0 || pos >= mPlayListLen) {
1391                 mPlayListLen = 0;
1392                 return;
1393             }
1394             mPlayPos = pos;
1395             Cursor mCursor = getContentResolver().query(
1396                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[] {
1397                         "_id"
1398                     }, "_id=" + mPlayList[mPlayPos], null, null);
1399             if (mCursor == null || mCursor.getCount() == 0) {
1400                 SystemClock.sleep(3000);
1401                 mCursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1402                         PROJECTION, "_id=" + mPlayList[mPlayPos], null, null);
1403             }
1404             if (mCursor != null) {
1405                 mCursor.close();
1406                 mCursor = null;
1407             }
1408             mOpenFailedCounter = 20;
1409             openCurrentAndNext();
1410             if (!mPlayer.isInitialized()) {
1411                 mPlayListLen = 0;
1412                 return;
1413             }
1414
1415             final long seekpos = mPreferences.getLong("seekpos", 0);
1416             seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
1417
1418             int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
1419             if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
1420                 repmode = REPEAT_NONE;
1421             }
1422             mRepeatMode = repmode;
1423
1424             int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
1425             if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
1426                 shufmode = SHUFFLE_NONE;
1427             }
1428             if (shufmode != SHUFFLE_NONE) {
1429                 q = mPreferences.getString("history", "");
1430                 qlen = q != null ? q.length() : 0;
1431                 if (qlen > 1) {
1432                     plen = 0;
1433                     n = 0;
1434                     shift = 0;
1435                     mHistory.clear();
1436                     for (int i = 0; i < qlen; i++) {
1437                         final char c = q.charAt(i);
1438                         if (c == ';') {
1439                             if (n >= mPlayListLen) {
1440                                 mHistory.clear();
1441                                 break;
1442                             }
1443                             mHistory.add(n);
1444                             n = 0;
1445                             shift = 0;
1446                         } else {
1447                             if (c >= '0' && c <= '9') {
1448                                 n += c - '0' << shift;
1449                             } else if (c >= 'a' && c <= 'f') {
1450                                 n += 10 + c - 'a' << shift;
1451                             } else {
1452                                 mHistory.clear();
1453                                 break;
1454                             }
1455                             shift += 4;
1456                         }
1457                     }
1458                 }
1459             }
1460             if (shufmode == SHUFFLE_AUTO) {
1461                 if (!makeAutoShuffleList()) {
1462                     shufmode = SHUFFLE_NONE;
1463                 }
1464             }
1465             mShuffleMode = shufmode;
1466         }
1467     }
1468
1469     /**
1470      * Opens a file and prepares it for playback
1471      *
1472      * @param path The path of the file to open
1473      */
1474     public boolean openFile(final String path) {
1475         synchronized (this) {
1476             if (path == null) {
1477                 return false;
1478             }
1479
1480             // If mCursor is null, try to associate path with a database cursor
1481             if (mCursor == null) {
1482                 final ContentResolver resolver = getContentResolver();
1483                 Uri uri;
1484                 String where;
1485                 String selectionArgs[];
1486                 if (path.startsWith("content://media/")) {
1487                     uri = Uri.parse(path);
1488                     where = null;
1489                     selectionArgs = null;
1490                 } else {
1491                     uri = MediaStore.Audio.Media.getContentUriForPath(path);
1492                     where = MediaStore.Audio.Media.DATA + "=?";
1493                     selectionArgs = new String[] {
1494                         path
1495                     };
1496                 }
1497                 try {
1498                     mCursor = resolver.query(uri, PROJECTION, where, selectionArgs, null);
1499                     if (mCursor != null) {
1500                         if (mCursor.getCount() == 0) {
1501                             mCursor.close();
1502                             mCursor = null;
1503                         } else {
1504                             mCursor.moveToNext();
1505                             ensurePlayListCapacity(1);
1506                             mPlayListLen = 1;
1507                             mPlayList[0] = mCursor.getLong(IDCOLIDX);
1508                             mPlayPos = 0;
1509                         }
1510                     }
1511                 } catch (final UnsupportedOperationException ex) {
1512                 }
1513             }
1514             mFileToPlay = path;
1515             mPlayer.setDataSource(mFileToPlay);
1516             if (mPlayer.isInitialized()) {
1517                 mOpenFailedCounter = 0;
1518                 return true;
1519             }
1520             stop(true);
1521             return false;
1522         }
1523     }
1524
1525     /**
1526      * Returns the audio session ID
1527      *
1528      * @return The current media player audio session ID
1529      */
1530     public int getAudioSessionId() {
1531         synchronized (this) {
1532             return mPlayer.getAudioSessionId();
1533         }
1534     }
1535
1536     /**
1537      * Indicates if the media storeage device has been mounted or not
1538      *
1539      * @return 1 if Intent.ACTION_MEDIA_MOUNTED is called, 0 otherwise
1540      */
1541     public int getMediaMountedCount() {
1542         return mMediaMountedCount;
1543     }
1544
1545     /**
1546      * Returns the shuffle mode
1547      *
1548      * @return The current shuffle mode (all, party, none)
1549      */
1550     public int getShuffleMode() {
1551         return mShuffleMode;
1552     }
1553
1554     /**
1555      * Returns the repeat mode
1556      *
1557      * @return The current repeat mode (all, one, none)
1558      */
1559     public int getRepeatMode() {
1560         return mRepeatMode;
1561     }
1562
1563     /**
1564      * Removes all instances of the track with the given ID from the playlist.
1565      *
1566      * @param id The id to be removed
1567      * @return how many instances of the track were removed
1568      */
1569     public int removeTrack(final long id) {
1570         int numremoved = 0;
1571         synchronized (this) {
1572             for (int i = 0; i < mPlayListLen; i++) {
1573                 if (mPlayList[i] == id) {
1574                     numremoved += removeTracksInternal(i, i);
1575                     i--;
1576                 }
1577             }
1578         }
1579         if (numremoved > 0) {
1580             notifyChange(QUEUE_CHANGED);
1581         }
1582         return numremoved;
1583     }
1584
1585     /**
1586      * Removes the range of tracks specified from the play list. If a file
1587      * within the range is the file currently being played, playback will move
1588      * to the next file after the range.
1589      *
1590      * @param first The first file to be removed
1591      * @param last The last file to be removed
1592      * @return the number of tracks deleted
1593      */
1594     public int removeTracks(final int first, final int last) {
1595         final int numremoved = removeTracksInternal(first, last);
1596         if (numremoved > 0) {
1597             notifyChange(QUEUE_CHANGED);
1598         }
1599         return numremoved;
1600     }
1601
1602     /**
1603      * Returns the position in the queue
1604      *
1605      * @return the current position in the queue
1606      */
1607     public int getQueuePosition() {
1608         synchronized (this) {
1609             return mPlayPos;
1610         }
1611     }
1612
1613     /**
1614      * Returns the path to current song
1615      *
1616      * @return The path to the current song
1617      */
1618     public String getPath() {
1619         synchronized (this) {
1620             if (mCursor == null) {
1621                 return null;
1622             }
1623             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
1624         }
1625     }
1626
1627     /**
1628      * Returns the album name
1629      *
1630      * @return The current song album Name
1631      */
1632     public String getAlbumName() {
1633         synchronized (this) {
1634             if (mCursor == null) {
1635                 return null;
1636             }
1637             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
1638         }
1639     }
1640
1641     /**
1642      * Returns the song name
1643      *
1644      * @return The current song name
1645      */
1646     public String getTrackName() {
1647         synchronized (this) {
1648             if (mCursor == null) {
1649                 return null;
1650             }
1651             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
1652         }
1653     }
1654
1655     /**
1656      * Returns the artist name
1657      *
1658      * @return The current song artist name
1659      */
1660     public String getArtistName() {
1661         synchronized (this) {
1662             if (mCursor == null) {
1663                 return null;
1664             }
1665             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));
1666         }
1667     }
1668
1669     /**
1670      * Returns the album ID
1671      *
1672      * @return The current song album ID
1673      */
1674     public long getAlbumId() {
1675         synchronized (this) {
1676             if (mCursor == null) {
1677                 return -1;
1678             }
1679             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
1680         }
1681     }
1682
1683     /**
1684      * Returns the artist ID
1685      *
1686      * @return The current song artist ID
1687      */
1688     public long getArtistId() {
1689         synchronized (this) {
1690             if (mCursor == null) {
1691                 return -1;
1692             }
1693             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));
1694         }
1695     }
1696
1697     /**
1698      * Returns the current audio ID
1699      *
1700      * @return The current track ID
1701      */
1702     public long getAudioId() {
1703         synchronized (this) {
1704             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1705                 return mPlayList[mPlayPos];
1706             }
1707         }
1708         return -1;
1709     }
1710
1711     /**
1712      * Seeks the current track to a specific time
1713      *
1714      * @param position The time to seek to
1715      * @return The time to play the track at
1716      */
1717     public long seek(long position) {
1718         if (mPlayer.isInitialized()) {
1719             if (position < 0) {
1720                 position = 0;
1721             } else if (position > mPlayer.duration()) {
1722                 position = mPlayer.duration();
1723             }
1724             return mPlayer.seek(position);
1725         }
1726         return -1;
1727     }
1728
1729     /**
1730      * Returns the current position in time of the currenttrack
1731      *
1732      * @return The current playback position in miliseconds
1733      */
1734     public long position() {
1735         if (mPlayer.isInitialized()) {
1736             return mPlayer.position();
1737         }
1738         return -1;
1739     }
1740
1741     /**
1742      * Returns the full duration of the current track
1743      *
1744      * @return The duration of the current track in miliseconds
1745      */
1746     public long duration() {
1747         if (mPlayer.isInitialized()) {
1748             return mPlayer.duration();
1749         }
1750         return -1;
1751     }
1752
1753     /**
1754      * Returns the queue
1755      *
1756      * @return The queue as a long[]
1757      */
1758     public long[] getQueue() {
1759         synchronized (this) {
1760             final int len = mPlayListLen;
1761             final long[] list = new long[len];
1762             for (int i = 0; i < len; i++) {
1763                 list[i] = mPlayList[i];
1764             }
1765             return list;
1766         }
1767     }
1768
1769     /**
1770      * @return True if music is playing, false otherwise
1771      */
1772     public boolean isPlaying() {
1773         return mIsSupposedToBePlaying;
1774     }
1775
1776     /**
1777      * True if the current track is a "favorite", false otherwise
1778      */
1779     public boolean isFavorite() {
1780         if (mFavoritesCache != null) {
1781             synchronized (this) {
1782                 final Long id = mFavoritesCache.getSongId(getAudioId());
1783                 return id != null ? true : false;
1784             }
1785         }
1786         return false;
1787     }
1788
1789     /**
1790      * Opens a list for playback
1791      *
1792      * @param list The list of tracks to open
1793      * @param position The position to start playback at
1794      */
1795     public void open(final long[] list, final int position) {
1796         synchronized (this) {
1797             if (mShuffleMode == SHUFFLE_AUTO) {
1798                 mShuffleMode = SHUFFLE_NORMAL;
1799             }
1800             final long oldId = getAudioId();
1801             final int listlength = list.length;
1802             boolean newlist = true;
1803             if (mPlayListLen == listlength) {
1804                 newlist = false;
1805                 for (int i = 0; i < listlength; i++) {
1806                     if (list[i] != mPlayList[i]) {
1807                         newlist = true;
1808                         break;
1809                     }
1810                 }
1811             }
1812             if (newlist) {
1813                 addToPlayList(list, -1);
1814                 notifyChange(QUEUE_CHANGED);
1815             }
1816             if (position >= 0) {
1817                 mPlayPos = position;
1818             } else {
1819                 mPlayPos = mShuffler.nextInt(mPlayListLen);
1820             }
1821             mHistory.clear();
1822             openCurrentAndNext();
1823             if (oldId != getAudioId()) {
1824                 notifyChange(META_CHANGED);
1825             }
1826         }
1827     }
1828
1829     /**
1830      * Stops playback.
1831      */
1832     public void stop() {
1833         stop(true);
1834     }
1835
1836     /**
1837      * Resumes or starts playback.
1838      */
1839     public void play() {
1840         int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
1841                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1842         if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1843             return;
1844         }
1845
1846         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
1847                 MediaButtonIntentReceiver.class.getName()));
1848
1849         if (mPlayer.isInitialized()) {
1850             final long duration = mPlayer.duration();
1851             if (mRepeatMode != REPEAT_CURRENT && duration > 2000
1852                     && mPlayer.position() >= duration - 2000) {
1853                 gotoNext(true);
1854             }
1855
1856             mPlayer.start();
1857             mPlayerHandler.removeMessages(FADEDOWN);
1858             mPlayerHandler.sendEmptyMessage(FADEUP);
1859
1860             if (!mIsSupposedToBePlaying) {
1861                 mIsSupposedToBePlaying = true;
1862                 notifyChange(PLAYSTATE_CHANGED);
1863             }
1864
1865             updateNotification();
1866         } else if (mPlayListLen <= 0) {
1867             setShuffleMode(SHUFFLE_AUTO);
1868         }
1869     }
1870
1871     /**
1872      * Temporarily pauses playback.
1873      */
1874     public void pause() {
1875         synchronized (this) {
1876             mPlayerHandler.removeMessages(FADEUP);
1877             if (mIsSupposedToBePlaying) {
1878                 mPlayer.pause();
1879                 gotoIdleState();
1880                 mIsSupposedToBePlaying = false;
1881                 notifyChange(PLAYSTATE_CHANGED);
1882             }
1883         }
1884     }
1885
1886     /**
1887      * Changes from the current track to the next track
1888      */
1889     public void gotoNext(final boolean force) {
1890         synchronized (this) {
1891             if (mPlayListLen <= 0) {
1892                 return;
1893             }
1894             final int pos = getNextPosition(force);
1895             if (pos < 0) {
1896                 gotoIdleState();
1897                 if (mIsSupposedToBePlaying) {
1898                     mIsSupposedToBePlaying = false;
1899                     notifyChange(PLAYSTATE_CHANGED);
1900                 }
1901                 return;
1902             }
1903             mPlayPos = pos;
1904             stop(false);
1905             mPlayPos = pos;
1906             openCurrentAndNext();
1907             play();
1908             notifyChange(META_CHANGED);
1909         }
1910     }
1911
1912     /**
1913      * Changes from the current track to the previous played track
1914      */
1915     public void prev() {
1916         synchronized (this) {
1917             if (mShuffleMode == SHUFFLE_NORMAL) {
1918                 // Go to previously-played track and remove it from the history
1919                 final int histsize = mHistory.size();
1920                 if (histsize == 0) {
1921                     return;
1922                 }
1923                 final Integer pos = mHistory.remove(histsize - 1);
1924                 mPlayPos = pos.intValue();
1925             } else {
1926                 if (mPlayPos > 0) {
1927                     mPlayPos--;
1928                 } else {
1929                     mPlayPos = mPlayListLen - 1;
1930                 }
1931             }
1932             stop(false);
1933             openCurrent();
1934             play();
1935             notifyChange(META_CHANGED);
1936         }
1937     }
1938
1939     /**
1940      * We don't want to open the current and next track when the user is using
1941      * the {@code #prev()} method because they won't be able to travel back to
1942      * the previously listened track if they're shuffling.
1943      */
1944     private void openCurrent() {
1945         openCurrentAndMaybeNext(false);
1946     }
1947
1948     /**
1949      * Toggles the current song as a favorite.
1950      */
1951     public void toggleFavorite() {
1952         if (mFavoritesCache != null) {
1953             synchronized (this) {
1954                 mFavoritesCache.toggleSong(getAudioId(), getTrackName(), getAlbumName(),
1955                         getArtistName());
1956             }
1957         }
1958     }
1959
1960     /**
1961      * Moves an item in the queue from one position to another
1962      *
1963      * @param from The position the item is currently at
1964      * @param to The position the item is being moved to
1965      */
1966     public void moveQueueItem(int index1, int index2) {
1967         synchronized (this) {
1968             if (index1 >= mPlayListLen) {
1969                 index1 = mPlayListLen - 1;
1970             }
1971             if (index2 >= mPlayListLen) {
1972                 index2 = mPlayListLen - 1;
1973             }
1974             if (index1 < index2) {
1975                 final long tmp = mPlayList[index1];
1976                 for (int i = index1; i < index2; i++) {
1977                     mPlayList[i] = mPlayList[i + 1];
1978                 }
1979                 mPlayList[index2] = tmp;
1980                 if (mPlayPos == index1) {
1981                     mPlayPos = index2;
1982                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
1983                     mPlayPos--;
1984                 }
1985             } else if (index2 < index1) {
1986                 final long tmp = mPlayList[index1];
1987                 for (int i = index1; i > index2; i--) {
1988                     mPlayList[i] = mPlayList[i - 1];
1989                 }
1990                 mPlayList[index2] = tmp;
1991                 if (mPlayPos == index1) {
1992                     mPlayPos = index2;
1993                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
1994                     mPlayPos++;
1995                 }
1996             }
1997             notifyChange(QUEUE_CHANGED);
1998         }
1999     }
2000
2001     /**
2002      * Sets the repeat mode
2003      *
2004      * @param repeatmode The repeat mode to use
2005      */
2006     public void setRepeatMode(final int repeatmode) {
2007         synchronized (this) {
2008             mRepeatMode = repeatmode;
2009             setNextTrack();
2010             saveQueue(false);
2011             notifyChange(REPEATMODE_CHANGED);
2012         }
2013     }
2014
2015     /**
2016      * Sets the shuffle mode
2017      *
2018      * @param shufflemode The shuffle mode to use
2019      */
2020     public void setShuffleMode(final int shufflemode) {
2021         synchronized (this) {
2022             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
2023                 return;
2024             }
2025             mShuffleMode = shufflemode;
2026             if (mShuffleMode == SHUFFLE_AUTO) {
2027                 if (makeAutoShuffleList()) {
2028                     mPlayListLen = 0;
2029                     doAutoShuffleUpdate();
2030                     mPlayPos = 0;
2031                     openCurrentAndNext();
2032                     play();
2033                     notifyChange(META_CHANGED);
2034                     return;
2035                 } else {
2036                     mShuffleMode = SHUFFLE_NONE;
2037                 }
2038             }
2039             saveQueue(false);
2040             notifyChange(SHUFFLEMODE_CHANGED);
2041         }
2042     }
2043
2044     /**
2045      * Sets the position of a track in the queue
2046      *
2047      * @param index The position to place the track
2048      */
2049     public void setQueuePosition(final int index) {
2050         synchronized (this) {
2051             stop(false);
2052             mPlayPos = index;
2053             openCurrentAndNext();
2054             play();
2055             notifyChange(META_CHANGED);
2056             if (mShuffleMode == SHUFFLE_AUTO) {
2057                 doAutoShuffleUpdate();
2058             }
2059         }
2060     }
2061
2062     /**
2063      * Queues a new list for playback
2064      *
2065      * @param list The list to queue
2066      * @param action The action to take
2067      */
2068     public void enqueue(final long[] list, final int action) {
2069         synchronized (this) {
2070             if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
2071                 addToPlayList(list, mPlayPos + 1);
2072                 notifyChange(QUEUE_CHANGED);
2073             } else {
2074                 addToPlayList(list, Integer.MAX_VALUE);
2075                 notifyChange(QUEUE_CHANGED);
2076                 if (action == NOW) {
2077                     mPlayPos = mPlayListLen - list.length;
2078                     openCurrentAndNext();
2079                     play();
2080                     notifyChange(META_CHANGED);
2081                     return;
2082                 }
2083             }
2084             if (mPlayPos < 0) {
2085                 mPlayPos = 0;
2086                 openCurrentAndNext();
2087                 play();
2088                 notifyChange(META_CHANGED);
2089             }
2090         }
2091     }
2092
2093     /**
2094      * Cycles through the different repeat modes
2095      */
2096     private void cycleRepeat() {
2097         if (mRepeatMode == REPEAT_NONE) {
2098             setRepeatMode(REPEAT_ALL);
2099         } else if (mRepeatMode == REPEAT_ALL) {
2100             setRepeatMode(REPEAT_CURRENT);
2101             if (mShuffleMode != SHUFFLE_NONE) {
2102                 setShuffleMode(SHUFFLE_NONE);
2103             }
2104         } else {
2105             setRepeatMode(REPEAT_NONE);
2106         }
2107     }
2108
2109     /**
2110      * Cycles through the different shuffle modes
2111      */
2112     private void cycleShuffle() {
2113         if (mShuffleMode == SHUFFLE_NONE) {
2114             setShuffleMode(SHUFFLE_NORMAL);
2115             if (mRepeatMode == REPEAT_CURRENT) {
2116                 setRepeatMode(REPEAT_ALL);
2117             }
2118         } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
2119             setShuffleMode(SHUFFLE_NONE);
2120         }
2121     }
2122
2123     /**
2124      * @return The album art for the current album.
2125      */
2126     public Bitmap getAlbumArt() {
2127         // Return the cached artwork
2128         final Bitmap bitmap = mImageFetcher.getArtwork(getAlbumName(),
2129                 getAlbumId(), getArtistName());
2130         return bitmap;
2131     }
2132
2133     /**
2134      * Called when one of the lists should refresh or requery.
2135      */
2136     public void refresh() {
2137         notifyChange(REFRESH);
2138     }
2139
2140     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2141         /**
2142          * {@inheritDoc}
2143          */
2144         @Override
2145         public void onReceive(final Context context, final Intent intent) {
2146             final String command = intent.getStringExtra(CMDNAME);
2147
2148             if (AppWidgetSmall.CMDAPPWIDGETUPDATE.equals(command)) {
2149                 final int[] small = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2150                 mAppWidgetSmall.performUpdate(MusicPlaybackService.this, small);
2151             } else if (AppWidgetLarge.CMDAPPWIDGETUPDATE.equals(command)) {
2152                 final int[] large = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2153                 mAppWidgetLarge.performUpdate(MusicPlaybackService.this, large);
2154             } else if (AppWidgetLargeAlternate.CMDAPPWIDGETUPDATE.equals(command)) {
2155                 final int[] largeAlt = intent
2156                         .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2157                 mAppWidgetLargeAlternate.performUpdate(MusicPlaybackService.this, largeAlt);
2158             } else if (RecentWidgetProvider.CMDAPPWIDGETUPDATE.equals(command)) {
2159                 final int[] recent = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2160                 mRecentWidgetProvider.performUpdate(MusicPlaybackService.this, recent);
2161             } else {
2162                 handleCommandIntent(intent);
2163             }
2164         }
2165     };
2166
2167     private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
2168         /**
2169          * {@inheritDoc}
2170          */
2171         @Override
2172         public void onAudioFocusChange(final int focusChange) {
2173             mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
2174         }
2175     };
2176
2177     private static final class DelayedHandler extends Handler {
2178         private final WeakReference<MusicPlaybackService> mService;
2179
2180         /**
2181          * Constructor of <code>DelayedHandler</code>
2182          *
2183          * @param service The service to use.
2184          */
2185         public DelayedHandler(final MusicPlaybackService service) {
2186             mService = new WeakReference<MusicPlaybackService>(service);
2187         }
2188
2189         /**
2190          * {@inheritDoc}
2191          */
2192         @Override
2193         public void handleMessage(final Message msg) {
2194             final MusicPlaybackService service = mService.get();
2195             if (service == null) {
2196                 return;
2197             }
2198             if (service.isPlaying()
2199                     || service.mPausedByTransientLossOfFocus
2200                     || service.mServiceInUse
2201                     || service.mPlayerHandler.hasMessages(TRACK_ENDED)) {
2202                 return;
2203             }
2204             service.saveQueue(true);
2205             service.stopSelf(service.mServiceStartId);
2206         }
2207     }
2208
2209     private static final class MusicPlayerHandler extends Handler {
2210         private final WeakReference<MusicPlaybackService> mService;
2211         private float mCurrentVolume = 1.0f;
2212
2213         /**
2214          * Constructor of <code>MusicPlayerHandler</code>
2215          *
2216          * @param service The service to use.
2217          * @param looper The thread to run on.
2218          */
2219         public MusicPlayerHandler(final MusicPlaybackService service, final Looper looper) {
2220             super(looper);
2221             mService = new WeakReference<MusicPlaybackService>(service);
2222         }
2223
2224         /**
2225          * {@inheritDoc}
2226          */
2227         @Override
2228         public void handleMessage(final Message msg) {
2229             final MusicPlaybackService service = mService.get();
2230             if (service == null) {
2231                 return;
2232             }
2233
2234             switch (msg.what) {
2235                 case FADEDOWN:
2236                     mCurrentVolume -= .05f;
2237                     if (mCurrentVolume > .2f) {
2238                         sendEmptyMessageDelayed(FADEDOWN, 10);
2239                     } else {
2240                         mCurrentVolume = .2f;
2241                     }
2242                     service.mPlayer.setVolume(mCurrentVolume);
2243                     break;
2244                 case FADEUP:
2245                     mCurrentVolume += .01f;
2246                     if (mCurrentVolume < 1.0f) {
2247                         sendEmptyMessageDelayed(FADEUP, 10);
2248                     } else {
2249                         mCurrentVolume = 1.0f;
2250                     }
2251                     service.mPlayer.setVolume(mCurrentVolume);
2252                     break;
2253                 case SERVER_DIED:
2254                     if (service.isPlaying()) {
2255                         service.gotoNext(true);
2256                     } else {
2257                         service.openCurrentAndNext();
2258                     }
2259                     break;
2260                 case TRACK_WENT_TO_NEXT:
2261                     service.mPlayPos = service.mNextPlayPos;
2262                     if (service.mCursor != null) {
2263                         service.mCursor.close();
2264                     }
2265                     service.mCursor = service.getCursorForId(service.mPlayList[service.mPlayPos]);
2266                     service.notifyChange(META_CHANGED);
2267                     service.updateNotification();
2268                     service.setNextTrack();
2269                     break;
2270                 case TRACK_ENDED:
2271                     if (service.mRepeatMode == REPEAT_CURRENT) {
2272                         service.seek(0);
2273                         service.play();
2274                     } else {
2275                         service.gotoNext(false);
2276                     }
2277                     break;
2278                 case RELEASE_WAKELOCK:
2279                     service.mWakeLock.release();
2280                     break;
2281                 case FOCUSCHANGE:
2282                     switch (msg.arg1) {
2283                         case AudioManager.AUDIOFOCUS_LOSS:
2284                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
2285                             if (service.isPlaying()) {
2286                                 service.mPausedByTransientLossOfFocus =
2287                                     msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
2288                             }
2289                             service.pause();
2290                             break;
2291                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
2292                             removeMessages(FADEUP);
2293                             sendEmptyMessage(FADEDOWN);
2294                             break;
2295                         case AudioManager.AUDIOFOCUS_GAIN:
2296                             if (!service.isPlaying()
2297                                     && service.mPausedByTransientLossOfFocus) {
2298                                 service.mPausedByTransientLossOfFocus = false;
2299                                 mCurrentVolume = 0f;
2300                                 service.mPlayer.setVolume(mCurrentVolume);
2301                                 service.play();
2302                             } else {
2303                                 removeMessages(FADEDOWN);
2304                                 sendEmptyMessage(FADEUP);
2305                             }
2306                             break;
2307                         default:
2308                     }
2309                     break;
2310                 default:
2311                     break;
2312             }
2313         }
2314     }
2315
2316     private static final class Shuffler {
2317
2318         private final LinkedList<Integer> mHistoryOfNumbers = new LinkedList<Integer>();
2319
2320         private final TreeSet<Integer> mPreviousNumbers = new TreeSet<Integer>();
2321
2322         private final Random mRandom = new Random();
2323
2324         private int mPrevious;
2325
2326         /**
2327          * Constructor of <code>Shuffler</code>
2328          */
2329         public Shuffler() {
2330             super();
2331         }
2332
2333         /**
2334          * @param interval The length the queue
2335          * @return The position of the next track to play
2336          */
2337         public int nextInt(final int interval) {
2338             int next;
2339             do {
2340                 next = mRandom.nextInt(interval);
2341             } while (next == mPrevious && interval > 1
2342                     && !mPreviousNumbers.contains(Integer.valueOf(next)));
2343             mPrevious = next;
2344             mHistoryOfNumbers.add(mPrevious);
2345             mPreviousNumbers.add(mPrevious);
2346             cleanUpHistory();
2347             return next;
2348         }
2349
2350         /**
2351          * Removes old tracks and cleans up the history preparing for new tracks
2352          * to be added to the mapping
2353          */
2354         private void cleanUpHistory() {
2355             if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {
2356                 for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {
2357                     mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());
2358                 }
2359             }
2360         }
2361     };
2362
2363     private static final class MultiPlayer implements MediaPlayer.OnErrorListener,
2364             MediaPlayer.OnCompletionListener {
2365
2366         private final WeakReference<MusicPlaybackService> mService;
2367
2368         private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
2369
2370         private MediaPlayer mNextMediaPlayer;
2371
2372         private Handler mHandler;
2373
2374         private boolean mIsInitialized = false;
2375
2376         /**
2377          * Constructor of <code>MultiPlayer</code>
2378          */
2379         public MultiPlayer(final MusicPlaybackService service) {
2380             mService = new WeakReference<MusicPlaybackService>(service);
2381             mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2382         }
2383
2384         /**
2385          * @param path The path of the file, or the http/rtsp URL of the stream
2386          *            you want to play
2387          */
2388         public void setDataSource(final String path) {
2389             mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
2390             if (mIsInitialized) {
2391                 setNextDataSource(null);
2392             }
2393         }
2394
2395         /**
2396          * @param player The {@link MediaPlayer} to use
2397          * @param path The path of the file, or the http/rtsp URL of the stream
2398          *            you want to play
2399          * @return True if the <code>player</code> has been prepared and is
2400          *         ready to play, false otherwise
2401          */
2402         private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
2403             try {
2404                 player.reset();
2405                 player.setOnPreparedListener(null);
2406                 if (path.startsWith("content://")) {
2407                     player.setDataSource(mService.get(), Uri.parse(path));
2408                 } else {
2409                     player.setDataSource(path);
2410                 }
2411                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
2412                 player.prepare();
2413             } catch (final IOException todo) {
2414                 // TODO: notify the user why the file couldn't be opened
2415                 return false;
2416             } catch (final IllegalArgumentException todo) {
2417                 // TODO: notify the user why the file couldn't be opened
2418                 return false;
2419             }
2420             player.setOnCompletionListener(this);
2421             player.setOnErrorListener(this);
2422             final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2423             intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2424             intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, mService.get().getPackageName());
2425             mService.get().sendBroadcast(intent);
2426             return true;
2427         }
2428
2429         /**
2430          * Set the MediaPlayer to start when this MediaPlayer finishes playback.
2431          *
2432          * @param path The path of the file, or the http/rtsp URL of the stream
2433          *            you want to play
2434          */
2435         public void setNextDataSource(final String path) {
2436             mCurrentMediaPlayer.setNextMediaPlayer(null);
2437             if (mNextMediaPlayer != null) {
2438                 mNextMediaPlayer.release();
2439                 mNextMediaPlayer = null;
2440             }
2441             if (path == null) {
2442                 return;
2443             }
2444             mNextMediaPlayer = new MediaPlayer();
2445             mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2446             mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
2447             if (setDataSourceImpl(mNextMediaPlayer, path)) {
2448                 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
2449             } else {
2450                 if (mNextMediaPlayer != null) {
2451                     mNextMediaPlayer.release();
2452                     mNextMediaPlayer = null;
2453                 }
2454             }
2455         }
2456
2457         /**
2458          * Sets the handler
2459          *
2460          * @param handler The handler to use
2461          */
2462         public void setHandler(final Handler handler) {
2463             mHandler = handler;
2464         }
2465
2466         /**
2467          * @return True if the player is ready to go, false otherwise
2468          */
2469         public boolean isInitialized() {
2470             return mIsInitialized;
2471         }
2472
2473         /**
2474          * Starts or resumes playback.
2475          */
2476         public void start() {
2477             mCurrentMediaPlayer.start();
2478         }
2479
2480         /**
2481          * Resets the MediaPlayer to its uninitialized state.
2482          */
2483         public void stop() {
2484             mCurrentMediaPlayer.reset();
2485             mIsInitialized = false;
2486         }
2487
2488         /**
2489          * Releases resources associated with this MediaPlayer object.
2490          */
2491         public void release() {
2492             stop();
2493             mCurrentMediaPlayer.release();
2494         }
2495
2496         /**
2497          * Pauses playback. Call start() to resume.
2498          */
2499         public void pause() {
2500             mCurrentMediaPlayer.pause();
2501         }
2502
2503         /**
2504          * Gets the duration of the file.
2505          *
2506          * @return The duration in milliseconds
2507          */
2508         public long duration() {
2509             return mCurrentMediaPlayer.getDuration();
2510         }
2511
2512         /**
2513          * Gets the current playback position.
2514          *
2515          * @return The current position in milliseconds
2516          */
2517         public long position() {
2518             return mCurrentMediaPlayer.getCurrentPosition();
2519         }
2520
2521         /**
2522          * Gets the current playback position.
2523          *
2524          * @param whereto The offset in milliseconds from the start to seek to
2525          * @return The offset in milliseconds from the start to seek to
2526          */
2527         public long seek(final long whereto) {
2528             mCurrentMediaPlayer.seekTo((int)whereto);
2529             return whereto;
2530         }
2531
2532         /**
2533          * Sets the volume on this player.
2534          *
2535          * @param vol Left and right volume scalar
2536          */
2537         public void setVolume(final float vol) {
2538             mCurrentMediaPlayer.setVolume(vol, vol);
2539         }
2540
2541         /**
2542          * Sets the audio session ID.
2543          *
2544          * @param sessionId The audio session ID
2545          */
2546         public void setAudioSessionId(final int sessionId) {
2547             mCurrentMediaPlayer.setAudioSessionId(sessionId);
2548         }
2549
2550         /**
2551          * Returns the audio session ID.
2552          *
2553          * @return The current audio session ID.
2554          */
2555         public int getAudioSessionId() {
2556             return mCurrentMediaPlayer.getAudioSessionId();
2557         }
2558
2559         /**
2560          * {@inheritDoc}
2561          */
2562         @Override
2563         public boolean onError(final MediaPlayer mp, final int what, final int extra) {
2564             switch (what) {
2565                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
2566                     mIsInitialized = false;
2567                     mCurrentMediaPlayer.release();
2568                     mCurrentMediaPlayer = new MediaPlayer();
2569                     mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2570                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
2571                     return true;
2572                 default:
2573                     break;
2574             }
2575             return false;
2576         }
2577
2578         /**
2579          * {@inheritDoc}
2580          */
2581         @Override
2582         public void onCompletion(final MediaPlayer mp) {
2583             if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
2584                 mCurrentMediaPlayer.release();
2585                 mCurrentMediaPlayer = mNextMediaPlayer;
2586                 mNextMediaPlayer = null;
2587                 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
2588             } else {
2589                 mService.get().mWakeLock.acquire(30000);
2590                 mHandler.sendEmptyMessage(TRACK_ENDED);
2591                 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
2592             }
2593         }
2594     }
2595
2596     private static final class ServiceStub extends IApolloService.Stub {
2597
2598         private final WeakReference<MusicPlaybackService> mService;
2599
2600         private ServiceStub(final MusicPlaybackService service) {
2601             mService = new WeakReference<MusicPlaybackService>(service);
2602         }
2603
2604         /**
2605          * {@inheritDoc}
2606          */
2607         @Override
2608         public void openFile(final String path) throws RemoteException {
2609             mService.get().openFile(path);
2610         }
2611
2612         /**
2613          * {@inheritDoc}
2614          */
2615         @Override
2616         public void open(final long[] list, final int position) throws RemoteException {
2617             mService.get().open(list, position);
2618         }
2619
2620         /**
2621          * {@inheritDoc}
2622          */
2623         @Override
2624         public void stop() throws RemoteException {
2625             mService.get().stop();
2626         }
2627
2628         /**
2629          * {@inheritDoc}
2630          */
2631         @Override
2632         public void pause() throws RemoteException {
2633             mService.get().pause();
2634         }
2635
2636         /**
2637          * {@inheritDoc}
2638          */
2639         @Override
2640         public void play() throws RemoteException {
2641             mService.get().play();
2642         }
2643
2644         /**
2645          * {@inheritDoc}
2646          */
2647         @Override
2648         public void prev() throws RemoteException {
2649             mService.get().prev();
2650         }
2651
2652         /**
2653          * {@inheritDoc}
2654          */
2655         @Override
2656         public void next() throws RemoteException {
2657             mService.get().gotoNext(true);
2658         }
2659
2660         /**
2661          * {@inheritDoc}
2662          */
2663         @Override
2664         public void enqueue(final long[] list, final int action) throws RemoteException {
2665             mService.get().enqueue(list, action);
2666         }
2667
2668         /**
2669          * {@inheritDoc}
2670          */
2671         @Override
2672         public void setQueuePosition(final int index) throws RemoteException {
2673             mService.get().setQueuePosition(index);
2674         }
2675
2676         /**
2677          * {@inheritDoc}
2678          */
2679         @Override
2680         public void setShuffleMode(final int shufflemode) throws RemoteException {
2681             mService.get().setShuffleMode(shufflemode);
2682         }
2683
2684         /**
2685          * {@inheritDoc}
2686          */
2687         @Override
2688         public void setRepeatMode(final int repeatmode) throws RemoteException {
2689             mService.get().setRepeatMode(repeatmode);
2690         }
2691
2692         /**
2693          * {@inheritDoc}
2694          */
2695         @Override
2696         public void moveQueueItem(final int from, final int to) throws RemoteException {
2697             mService.get().moveQueueItem(from, to);
2698         }
2699
2700         /**
2701          * {@inheritDoc}
2702          */
2703         @Override
2704         public void toggleFavorite() throws RemoteException {
2705             mService.get().toggleFavorite();
2706         }
2707
2708         /**
2709          * {@inheritDoc}
2710          */
2711         @Override
2712         public void refresh() throws RemoteException {
2713             mService.get().refresh();
2714         }
2715
2716         /**
2717          * {@inheritDoc}
2718          */
2719         @Override
2720         public boolean isFavorite() throws RemoteException {
2721             return mService.get().isFavorite();
2722         }
2723
2724         /**
2725          * {@inheritDoc}
2726          */
2727         @Override
2728         public boolean isPlaying() throws RemoteException {
2729             return mService.get().isPlaying();
2730         }
2731
2732         /**
2733          * {@inheritDoc}
2734          */
2735         @Override
2736         public long[] getQueue() throws RemoteException {
2737             return mService.get().getQueue();
2738         }
2739
2740         /**
2741          * {@inheritDoc}
2742          */
2743         @Override
2744         public long duration() throws RemoteException {
2745             return mService.get().duration();
2746         }
2747
2748         /**
2749          * {@inheritDoc}
2750          */
2751         @Override
2752         public long position() throws RemoteException {
2753             return mService.get().position();
2754         }
2755
2756         /**
2757          * {@inheritDoc}
2758          */
2759         @Override
2760         public long seek(final long position) throws RemoteException {
2761             return mService.get().seek(position);
2762         }
2763
2764         /**
2765          * {@inheritDoc}
2766          */
2767         @Override
2768         public long getAudioId() throws RemoteException {
2769             return mService.get().getAudioId();
2770         }
2771
2772         /**
2773          * {@inheritDoc}
2774          */
2775         @Override
2776         public long getArtistId() throws RemoteException {
2777             return mService.get().getArtistId();
2778         }
2779
2780         /**
2781          * {@inheritDoc}
2782          */
2783         @Override
2784         public long getAlbumId() throws RemoteException {
2785             return mService.get().getAlbumId();
2786         }
2787
2788         /**
2789          * {@inheritDoc}
2790          */
2791         @Override
2792         public String getArtistName() throws RemoteException {
2793             return mService.get().getArtistName();
2794         }
2795
2796         /**
2797          * {@inheritDoc}
2798          */
2799         @Override
2800         public String getTrackName() throws RemoteException {
2801             return mService.get().getTrackName();
2802         }
2803
2804         /**
2805          * {@inheritDoc}
2806          */
2807         @Override
2808         public String getAlbumName() throws RemoteException {
2809             return mService.get().getAlbumName();
2810         }
2811
2812         /**
2813          * {@inheritDoc}
2814          */
2815         @Override
2816         public String getPath() throws RemoteException {
2817             return mService.get().getPath();
2818         }
2819
2820         /**
2821          * {@inheritDoc}
2822          */
2823         @Override
2824         public int getQueuePosition() throws RemoteException {
2825             return mService.get().getQueuePosition();
2826         }
2827
2828         /**
2829          * {@inheritDoc}
2830          */
2831         @Override
2832         public int getShuffleMode() throws RemoteException {
2833             return mService.get().getShuffleMode();
2834         }
2835
2836         /**
2837          * {@inheritDoc}
2838          */
2839         @Override
2840         public int getRepeatMode() throws RemoteException {
2841             return mService.get().getRepeatMode();
2842         }
2843
2844         /**
2845          * {@inheritDoc}
2846          */
2847         @Override
2848         public int removeTracks(final int first, final int last) throws RemoteException {
2849             return mService.get().removeTracks(first, last);
2850         }
2851
2852         /**
2853          * {@inheritDoc}
2854          */
2855         @Override
2856         public int removeTrack(final long id) throws RemoteException {
2857             return mService.get().removeTrack(id);
2858         }
2859
2860         /**
2861          * {@inheritDoc}
2862          */
2863         @Override
2864         public int getMediaMountedCount() throws RemoteException {
2865             return mService.get().getMediaMountedCount();
2866         }
2867
2868         /**
2869          * {@inheritDoc}
2870          */
2871         @Override
2872         public int getAudioSessionId() throws RemoteException {
2873             return mService.get().getAudioSessionId();
2874         }
2875
2876     }
2877
2878 }