OSDN Git Service

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