OSDN Git Service

91859fa080c711039c4b0e882dbfa6916f71c1ce
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / MusicPlaybackService.java
1 /*
2  * Copyright (C) 2012 Andrew Neal
3  * Copyright (C) 2014-2016 The CyanogenMod Project
4  * Licensed under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with the
6  * License. You may obtain a copy of the License at
7  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
8  * or agreed to in writing, software distributed under the License is
9  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
10  * KIND, either express or implied. See the License for the specific language
11  * governing permissions and limitations under the License.
12  */
13
14 package org.lineageos.eleven;
15
16 import android.Manifest.permission;
17 import android.annotation.NonNull;
18 import android.annotation.SuppressLint;
19 import android.app.AlarmManager;
20 import android.app.Notification;
21 import android.app.NotificationManager;
22 import android.app.PendingIntent;
23 import android.app.Service;
24 import android.appwidget.AppWidgetManager;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager;
33 import android.database.ContentObserver;
34 import android.database.Cursor;
35 import android.database.MatrixCursor;
36 import android.graphics.Bitmap;
37 import android.hardware.SensorManager;
38 import android.media.AudioManager;
39 import android.media.AudioManager.OnAudioFocusChangeListener;
40 import android.media.MediaDescription;
41 import android.media.MediaMetadata;
42 import android.media.MediaPlayer;
43 import android.media.audiofx.AudioEffect;
44 import android.media.session.MediaSession;
45 import android.media.session.PlaybackState;
46 import android.net.Uri;
47 import android.os.AsyncTask;
48 import android.os.Handler;
49 import android.os.HandlerThread;
50 import android.os.IBinder;
51 import android.os.Looper;
52 import android.os.Message;
53 import android.os.PowerManager;
54 import android.os.RemoteException;
55 import android.os.SystemClock;
56 import android.provider.MediaStore;
57 import android.provider.MediaStore.Audio.AlbumColumns;
58 import android.provider.MediaStore.Audio.AudioColumns;
59 import android.text.TextUtils;
60 import android.util.Log;
61 import android.util.LongSparseArray;
62 import android.view.KeyEvent;
63
64 import org.lineageos.eleven.Config.IdType;
65 import org.lineageos.eleven.appwidgets.AppWidgetLarge;
66 import org.lineageos.eleven.appwidgets.AppWidgetLargeAlternate;
67 import org.lineageos.eleven.appwidgets.AppWidgetSmall;
68 import org.lineageos.eleven.cache.ImageCache;
69 import org.lineageos.eleven.cache.ImageFetcher;
70 import org.lineageos.eleven.provider.MusicPlaybackState;
71 import org.lineageos.eleven.provider.RecentStore;
72 import org.lineageos.eleven.provider.SongPlayCount;
73 import org.lineageos.eleven.service.MusicPlaybackTrack;
74 import org.lineageos.eleven.utils.BitmapWithColors;
75 import org.lineageos.eleven.utils.Lists;
76 import org.lineageos.eleven.utils.PreferenceUtils;
77 import org.lineageos.eleven.utils.ShakeDetector;
78 import org.lineageos.eleven.utils.SrtManager;
79
80 import java.io.File;
81 import java.io.IOException;
82 import java.lang.ref.WeakReference;
83 import java.util.ArrayList;
84 import java.util.Arrays;
85 import java.util.LinkedList;
86 import java.util.List;
87 import java.util.ListIterator;
88 import java.util.Random;
89 import java.util.TreeSet;
90
91 /**
92  * A backbround {@link Service} used to keep music playing between activities
93  * and when the user moves Apollo into the background.
94  */
95 @SuppressLint("NewApi")
96 public class MusicPlaybackService extends Service {
97     private static final String TAG = "MusicPlaybackService";
98     private static final boolean D = false;
99
100     /**
101      * Indicates that the music has paused or resumed
102      */
103     public static final String PLAYSTATE_CHANGED = "org.lineageos.eleven.playstatechanged";
104
105     /**
106      * Indicates that music playback position within
107      * a title was changed
108      */
109     public static final String POSITION_CHANGED = "org.lineageos.eleven.positionchanged";
110
111     /**
112      * Indicates the meta data has changed in some way, like a track change
113      */
114     public static final String META_CHANGED = "org.lineageos.eleven.metachanged";
115
116     /**
117      * Indicates the queue has been updated
118      */
119     public static final String QUEUE_CHANGED = "org.lineageos.eleven.queuechanged";
120
121     /**
122      * Indicates the queue has been updated
123      */
124     public static final String PLAYLIST_CHANGED = "org.lineageos.eleven.playlistchanged";
125
126     /**
127      * Indicates the repeat mode changed
128      */
129     public static final String REPEATMODE_CHANGED = "org.lineageos.eleven.repeatmodechanged";
130
131     /**
132      * Indicates the shuffle mode changed
133      */
134     public static final String SHUFFLEMODE_CHANGED = "org.lineageos.eleven.shufflemodechanged";
135
136     /**
137      * Indicates the track fails to play
138      */
139     public static final String TRACK_ERROR = "org.lineageos.eleven.trackerror";
140
141     /**
142      * For backwards compatibility reasons, also provide sticky
143      * broadcasts under the music package
144      */
145     public static final String ELEVEN_PACKAGE_NAME = "org.lineageos.eleven";
146     public static final String MUSIC_PACKAGE_NAME = "com.android.music";
147
148     /**
149      * Called to indicate a general service commmand. Used in
150      * {@link MediaButtonIntentReceiver}
151      */
152     public static final String SERVICECMD = "org.lineageos.eleven.musicservicecommand";
153
154     /**
155      * Called to go toggle between pausing and playing the music
156      */
157     public static final String TOGGLEPAUSE_ACTION = "org.lineageos.eleven.togglepause";
158
159     /**
160      * Called to go to pause the playback
161      */
162     public static final String PAUSE_ACTION = "org.lineageos.eleven.pause";
163
164     /**
165      * Called to go to stop the playback
166      */
167     public static final String STOP_ACTION = "org.lineageos.eleven.stop";
168
169     /**
170      * Called to go to the previous track or the beginning of the track if partway through the track
171      */
172     public static final String PREVIOUS_ACTION = "org.lineageos.eleven.previous";
173
174     /**
175      * Called to go to the previous track regardless of how far in the current track the playback is
176      */
177     public static final String PREVIOUS_FORCE_ACTION = "org.lineageos.eleven.previous.force";
178
179     /**
180      * Called to go to the next track
181      */
182     public static final String NEXT_ACTION = "org.lineageos.eleven.next";
183
184     /**
185      * Called to change the repeat mode
186      */
187     public static final String REPEAT_ACTION = "org.lineageos.eleven.repeat";
188
189     /**
190      * Called to change the shuffle mode
191      */
192     public static final String SHUFFLE_ACTION = "org.lineageos.eleven.shuffle";
193
194     public static final String FROM_MEDIA_BUTTON = "frommediabutton";
195
196     public static final String TIMESTAMP = "timestamp";
197
198     /**
199      * Used to easily notify a list that it should refresh. i.e. A playlist
200      * changes
201      */
202     public static final String REFRESH = "org.lineageos.eleven.refresh";
203
204     /**
205      * Used by the alarm intent to shutdown the service after being idle
206      */
207     private static final String SHUTDOWN = "org.lineageos.eleven.shutdown";
208
209     /**
210      * Called to notify of a timed text
211      */
212     public static final String NEW_LYRICS = "org.lineageos.eleven.lyrics";
213
214     /**
215      * Called to update the remote control client
216      */
217     public static final String UPDATE_LOCKSCREEN = "org.lineageos.eleven.updatelockscreen";
218
219     public static final String CMDNAME = "command";
220
221     public static final String CMDTOGGLEPAUSE = "togglepause";
222
223     public static final String CMDSTOP = "stop";
224
225     public static final String CMDPAUSE = "pause";
226
227     public static final String CMDPLAY = "play";
228
229     public static final String CMDPREVIOUS = "previous";
230
231     public static final String CMDNEXT = "next";
232
233     public static final String CMDHEADSETHOOK = "headsethook";
234
235     private static final int IDCOLIDX = 0;
236
237     /**
238      * Moves a list to the next position in the queue
239      */
240     public static final int NEXT = 2;
241
242     /**
243      * Moves a list to the last position in the queue
244      */
245     public static final int LAST = 3;
246
247     /**
248      * Shuffles no songs, turns shuffling off
249      */
250     public static final int SHUFFLE_NONE = 0;
251
252     /**
253      * Shuffles all songs
254      */
255     public static final int SHUFFLE_NORMAL = 1;
256
257     /**
258      * Party shuffle
259      */
260     public static final int SHUFFLE_AUTO = 2;
261
262     /**
263      * Turns repeat off
264      */
265     public static final int REPEAT_NONE = 0;
266
267     /**
268      * Repeats the current track in a list
269      */
270     public static final int REPEAT_CURRENT = 1;
271
272     /**
273      * Repeats all the tracks in a list
274      */
275     public static final int REPEAT_ALL = 2;
276
277     /**
278      * Indicates when the track ends
279      */
280     private static final int TRACK_ENDED = 1;
281
282     /**
283      * Indicates that the current track was changed the next track
284      */
285     private static final int TRACK_WENT_TO_NEXT = 2;
286
287     /**
288      * Indicates the player died
289      */
290     private static final int SERVER_DIED = 3;
291
292     /**
293      * Indicates some sort of focus change, maybe a phone call
294      */
295     private static final int FOCUSCHANGE = 4;
296
297     /**
298      * Indicates to fade the volume down
299      */
300     private static final int FADEDOWN = 5;
301
302     /**
303      * Indicates to fade the volume back up
304      */
305     private static final int FADEUP = 6;
306
307     /**
308      * Notifies that there is a new timed text string
309      */
310     private static final int LYRICS = 7;
311
312     /**
313      * Indicates a headset hook key event
314      */
315     private static final int HEADSET_HOOK_EVENT = 8;
316
317     /**
318      * Indicates waiting for another headset hook event has timed out
319      */
320     private static final int HEADSET_HOOK_MULTI_CLICK_TIMEOUT = 9;
321
322     /**
323      * Idle time before stopping the foreground notfication (5 minutes)
324      */
325     private static final int IDLE_DELAY = 5 * 60 * 1000;
326
327     /**
328      * Song play time used as threshold for rewinding to the beginning of the
329      * track instead of skipping to the previous track when getting the PREVIOUS
330      * command
331      */
332     private static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 3000;
333
334     /**
335      * The max size allowed for the track history
336      * TODO: Comeback and rewrite/fix all the whole queue code bugs after demo
337      * https://cyanogen.atlassian.net/browse/MUSIC-175
338      * https://cyanogen.atlassian.net/browse/MUSIC-44
339      */
340     public static final int MAX_HISTORY_SIZE = 1000;
341
342     public interface TrackErrorExtra {
343         /**
344          * Name of the track that was unable to play
345          */
346         public static final String TRACK_NAME = "trackname";
347     }
348
349     /**
350      * The columns used to retrieve any info from the current track
351      */
352     private static final String[] PROJECTION = new String[] {
353             "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
354             MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
355             MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
356             MediaStore.Audio.Media.ARTIST_ID
357     };
358
359     /**
360      * The columns used to retrieve any info from the current album
361      */
362     private static final String[] ALBUM_PROJECTION = new String[] {
363             MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
364             MediaStore.Audio.Albums.LAST_YEAR
365     };
366
367     /**
368      * Keeps a mapping of the track history
369      */
370     private static LinkedList<Integer> mHistory = Lists.newLinkedList();
371
372     /**
373      * Used to shuffle the tracks
374      */
375     private static final Shuffler mShuffler = new Shuffler();
376
377     /**
378      * Service stub
379      */
380     private final IBinder mBinder = new ServiceStub(this);
381
382     /**
383      * 4x1 widget
384      */
385     private final AppWidgetSmall mAppWidgetSmall = AppWidgetSmall.getInstance();
386
387     /**
388      * 4x2 widget
389      */
390     private final AppWidgetLarge mAppWidgetLarge = AppWidgetLarge.getInstance();
391
392     /**
393      * 4x2 alternate widget
394      */
395     private final AppWidgetLargeAlternate mAppWidgetLargeAlternate = AppWidgetLargeAlternate
396             .getInstance();
397
398     /**
399      * The media player
400      */
401     private MultiPlayer mPlayer;
402
403     /**
404      * The path of the current file to play
405      */
406     private String mFileToPlay;
407
408     /**
409      * Alarm intent for removing the notification when nothing is playing
410      * for some time
411      */
412     private AlarmManager mAlarmManager;
413     private PendingIntent mShutdownIntent;
414     private boolean mShutdownScheduled;
415
416     private NotificationManager mNotificationManager;
417
418     /**
419      * The cursor used to retrieve info on the current track and run the
420      * necessary queries to play audio files
421      */
422     private Cursor mCursor;
423
424     /**
425      * The cursor used to retrieve info on the album the current track is
426      * part of, if any.
427      */
428     private Cursor mAlbumCursor;
429
430     /**
431      * Monitors the audio state
432      */
433     private AudioManager mAudioManager;
434
435     /**
436      * Settings used to save and retrieve the queue and history
437      */
438     private SharedPreferences mPreferences;
439
440     /**
441      * Used to know when the service is active
442      */
443     private boolean mServiceInUse = false;
444
445     /**
446      * Used to know if something should be playing or not
447      */
448     private boolean mIsSupposedToBePlaying = false;
449
450     /**
451      * Gets the last played time to determine whether we still want notifications or not
452      */
453     private long mLastPlayedTime;
454
455     private int mNotifyMode = NOTIFY_MODE_NONE;
456     private long mNotificationPostTime = 0;
457
458     private static final int NOTIFY_MODE_NONE = 0;
459     private static final int NOTIFY_MODE_FOREGROUND = 1;
460     private static final int NOTIFY_MODE_BACKGROUND = 2;
461
462     /**
463      * Used to indicate if the queue can be saved
464      */
465     private boolean mQueueIsSaveable = true;
466
467     /**
468      * Used to track what type of audio focus loss caused the playback to pause
469      */
470     private boolean mPausedByTransientLossOfFocus = false;
471
472     /**
473      * Lock screen controls
474      */
475     private MediaSession mSession;
476
477     // We use this to distinguish between different cards when saving/restoring
478     // playlists
479     private int mCardId;
480
481     private int mPlayPos = -1;
482
483     private int mNextPlayPos = -1;
484
485     private int mOpenFailedCounter = 0;
486
487     private int mMediaMountedCount = 0;
488
489     private int mShuffleMode = SHUFFLE_NONE;
490
491     private int mRepeatMode = REPEAT_NONE;
492
493     private int mServiceStartId = -1;
494
495     private String mLyrics;
496
497     private ArrayList<MusicPlaybackTrack> mPlaylist = new ArrayList<MusicPlaybackTrack>(100);
498
499     private long[] mAutoShuffleList = null;
500
501     private MusicPlayerHandler mPlayerHandler;
502     private HandlerThread mHandlerThread;
503
504     private BroadcastReceiver mUnmountReceiver = null;
505
506     // to improve perf, instead of hitting the disk cache or file cache, store the bitmaps in memory
507     private String mCachedKey;
508     private BitmapWithColors[] mCachedBitmapWithColors = new BitmapWithColors[2];
509
510     private QueueUpdateTask mQueueUpdateTask;
511
512     /**
513      * Image cache
514      */
515     private ImageFetcher mImageFetcher;
516
517     /**
518      * Recently listened database
519      */
520     private RecentStore mRecentsCache;
521
522     /**
523      * The song play count database
524      */
525     private SongPlayCount mSongPlayCountCache;
526
527     /**
528      * Stores the playback state
529      */
530     private MusicPlaybackState mPlaybackStateStore;
531
532     /**
533      * Shake detector class used for shake to switch song feature
534      */
535     private ShakeDetector mShakeDetector;
536
537     /**
538      * Switch for displaying album art on lockscreen
539      */
540     private boolean mShowAlbumArtOnLockscreen;
541
542     private boolean mReadGranted = false;
543
544     private PowerManager.WakeLock mHeadsetHookWakeLock;
545
546     private ShakeDetector.Listener mShakeDetectorListener=new ShakeDetector.Listener() {
547
548         @Override
549         public void hearShake() {
550             /*
551              * on shake detect, play next song
552              */
553             if (D) {
554                 Log.d(TAG,"Shake detected!!!");
555             }
556             gotoNext(true);
557         }
558     };
559
560     /**
561      * {@inheritDoc}
562      */
563     @Override
564     public IBinder onBind(final Intent intent) {
565         if (D) Log.d(TAG, "Service bound, intent = " + intent);
566         cancelShutdown();
567         mServiceInUse = true;
568         return mBinder;
569     }
570
571     /**
572      * {@inheritDoc}
573      */
574     @Override
575     public boolean onUnbind(final Intent intent) {
576         if (D) Log.d(TAG, "Service unbound");
577         mServiceInUse = false;
578         saveQueue(true);
579
580         if (mReadGranted) {
581             if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
582                 // Something is currently playing, or will be playing once
583                 // an in-progress action requesting audio focus ends, so don't stop
584                 // the service now.
585                 return true;
586
587                 // If there is a playlist but playback is paused, then wait a while
588                 // before stopping the service, so that pause/resume isn't slow.
589                 // Also delay stopping the service if we're transitioning between
590                 // tracks.
591             } else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
592                 scheduleDelayedShutdown();
593                 return true;
594             }
595         }
596         stopSelf(mServiceStartId);
597
598         return true;
599     }
600
601     /**
602      * {@inheritDoc}
603      */
604     @Override
605     public void onRebind(final Intent intent) {
606         cancelShutdown();
607         mServiceInUse = true;
608     }
609
610     /**
611      * {@inheritDoc}
612      */
613     @Override
614     public void onCreate() {
615         if (D) Log.d(TAG, "Creating service");
616         super.onCreate();
617
618         if (checkSelfPermission(permission.READ_EXTERNAL_STORAGE) !=
619                 PackageManager.PERMISSION_GRANTED) {
620             stopSelf();
621             return;
622         } else {
623             mReadGranted = true;
624         }
625
626         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
627
628         // Initialize the favorites and recents databases
629         mRecentsCache = RecentStore.getInstance(this);
630
631         // gets the song play count cache
632         mSongPlayCountCache = SongPlayCount.getInstance(this);
633
634         // gets a pointer to the playback state store
635         mPlaybackStateStore = MusicPlaybackState.getInstance(this);
636
637         // Initialize the image fetcher
638         mImageFetcher = ImageFetcher.getInstance(this);
639         // Initialize the image cache
640         mImageFetcher.setImageCache(ImageCache.getInstance(this));
641
642         // Start up the thread running the service. Note that we create a
643         // separate thread because the service normally runs in the process's
644         // main thread, which we don't want to block. We also make it
645         // background priority so CPU-intensive work will not disrupt the UI.
646         mHandlerThread = new HandlerThread("MusicPlayerHandler",
647                 android.os.Process.THREAD_PRIORITY_BACKGROUND);
648         mHandlerThread.start();
649
650         // Initialize the handler
651         mPlayerHandler = new MusicPlayerHandler(this, mHandlerThread.getLooper());
652
653         // Initialize the audio manager and register any headset controls for
654         // playback
655         mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
656
657         // Use the remote control APIs to set the playback state
658         setUpMediaSession();
659
660         // Initialize the preferences
661         mPreferences = getSharedPreferences("Service", 0);
662         mCardId = getCardId();
663
664         mShowAlbumArtOnLockscreen = mPreferences.getBoolean(
665                 PreferenceUtils.SHOW_ALBUM_ART_ON_LOCKSCREEN, true);
666         setShakeToPlayEnabled(mPreferences.getBoolean(PreferenceUtils.SHAKE_TO_PLAY, false));
667
668         mRepeatMode = mPreferences.getInt("repeatmode", REPEAT_NONE);
669         mShuffleMode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
670
671         registerExternalStorageListener();
672
673         // Initialize the media player
674         mPlayer = new MultiPlayer(this);
675         mPlayer.setHandler(mPlayerHandler);
676
677         // Initialize the intent filter and each action
678         final IntentFilter filter = new IntentFilter();
679         filter.addAction(SERVICECMD);
680         filter.addAction(TOGGLEPAUSE_ACTION);
681         filter.addAction(PAUSE_ACTION);
682         filter.addAction(STOP_ACTION);
683         filter.addAction(NEXT_ACTION);
684         filter.addAction(PREVIOUS_ACTION);
685         filter.addAction(PREVIOUS_FORCE_ACTION);
686         filter.addAction(REPEAT_ACTION);
687         filter.addAction(SHUFFLE_ACTION);
688         // Attach the broadcast listener
689         registerReceiver(mIntentReceiver, filter);
690
691         // Get events when MediaStore content changes
692         mMediaStoreObserver = new MediaStoreObserver(mPlayerHandler);
693         getContentResolver().registerContentObserver(
694                 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mMediaStoreObserver);
695         getContentResolver().registerContentObserver(
696                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);
697
698         // Initialize the delayed shutdown intent
699         final Intent shutdownIntent = new Intent(this, MusicPlaybackService.class);
700         shutdownIntent.setAction(SHUTDOWN);
701
702         mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
703         mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);
704
705         // Listen for the idle state
706         scheduleDelayedShutdown();
707
708         // Bring the queue back
709         reloadQueue();
710         notifyChange(QUEUE_CHANGED);
711         notifyChange(META_CHANGED);
712     }
713
714     private void setUpMediaSession() {
715         mSession = new MediaSession(this, "Eleven");
716         mSession.setCallback(new MediaSession.Callback() {
717             @Override
718             public void onPause() {
719                 pause();
720                 mPausedByTransientLossOfFocus = false;
721             }
722             @Override
723             public void onPlay() {
724                 play();
725             }
726             @Override
727             public void onSeekTo(long pos) {
728                 seek(pos);
729             }
730             @Override
731             public void onSkipToNext() {
732                 gotoNext(true);
733             }
734             @Override
735             public void onSkipToPrevious() {
736                 prev(false);
737             }
738             @Override
739             public void onStop() {
740                 pause();
741                 mPausedByTransientLossOfFocus = false;
742                 seek(0);
743                 releaseServiceUiAndStop();
744             }
745             @Override
746             public void onSkipToQueueItem(long id) {
747                 setQueuePosition((int) id);
748             }
749             @Override
750             public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
751                 if (Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
752                     KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
753                     if (ke != null && ke.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) {
754                         if (ke.getAction() == KeyEvent.ACTION_UP) {
755                             handleHeadsetHookClick(ke.getEventTime());
756                         }
757                         return true;
758                     }
759                 }
760                 return super.onMediaButtonEvent(mediaButtonIntent);
761             }
762         });
763
764         PendingIntent pi = PendingIntent.getBroadcast(this, 0,
765                 new Intent(this, MediaButtonIntentReceiver.class),
766                 PendingIntent.FLAG_UPDATE_CURRENT);
767         mSession.setMediaButtonReceiver(pi);
768
769         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
770                 | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
771     }
772
773     /**
774      * {@inheritDoc}
775      */
776     @Override
777     public void onDestroy() {
778         if (D) Log.d(TAG, "Destroying service");
779         if (!mReadGranted) {
780             return;
781         }
782         super.onDestroy();
783         // Remove any sound effects
784         final Intent audioEffectsIntent = new Intent(
785                 AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
786         audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
787         audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
788         sendBroadcast(audioEffectsIntent);
789
790         // remove any pending alarms
791         mAlarmManager.cancel(mShutdownIntent);
792
793         // Remove any callbacks from the handler
794         mPlayerHandler.removeCallbacksAndMessages(null);
795         // quit the thread so that anything that gets posted won't run
796         mHandlerThread.quitSafely();
797
798         // Release the player
799         mPlayer.release();
800         mPlayer = null;
801
802         // Remove the audio focus listener and lock screen controls
803         mAudioManager.abandonAudioFocus(mAudioFocusListener);
804         mSession.release();
805
806         // remove the media store observer
807         getContentResolver().unregisterContentObserver(mMediaStoreObserver);
808
809         // Close the cursor
810         closeCursor();
811
812         // Unregister the mount listener
813         unregisterReceiver(mIntentReceiver);
814         if (mUnmountReceiver != null) {
815             unregisterReceiver(mUnmountReceiver);
816             mUnmountReceiver = null;
817         }
818
819         // deinitialize shake detector
820         stopShakeDetector(true);
821     }
822
823     /**
824      * {@inheritDoc}
825      */
826     @Override
827     public int onStartCommand(final Intent intent, final int flags, final int startId) {
828         if (D) Log.d(TAG, "Got new intent " + intent + ", startId = " + startId);
829         mServiceStartId = startId;
830
831         if (intent != null) {
832             final String action = intent.getAction();
833
834             if (SHUTDOWN.equals(action)) {
835                 mShutdownScheduled = false;
836                 releaseServiceUiAndStop();
837                 return START_NOT_STICKY;
838             }
839
840             handleCommandIntent(intent);
841         }
842
843         // Make sure the service will shut down on its own if it was
844         // just started but not bound to and nothing is playing
845         scheduleDelayedShutdown();
846
847         if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {
848             MediaButtonIntentReceiver.completeWakefulIntent(intent);
849         }
850
851         return START_NOT_STICKY;
852     }
853
854     private void releaseServiceUiAndStop() {
855         if (isPlaying()
856                 || mPausedByTransientLossOfFocus
857                 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
858             return;
859         }
860
861         if (D) Log.d(TAG, "Nothing is playing anymore, releasing notification");
862         cancelNotification();
863         mAudioManager.abandonAudioFocus(mAudioFocusListener);
864         mSession.setActive(false);
865
866         if (!mServiceInUse) {
867             saveQueue(true);
868             stopSelf(mServiceStartId);
869         }
870     }
871
872     private void handleCommandIntent(Intent intent) {
873         final String action = intent.getAction();
874         final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
875
876         if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
877
878         if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
879             gotoNext(true);
880         } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
881                 || PREVIOUS_FORCE_ACTION.equals(action)) {
882             prev(PREVIOUS_FORCE_ACTION.equals(action));
883         } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
884             togglePlayPause();
885         } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
886             pause();
887             mPausedByTransientLossOfFocus = false;
888         } else if (CMDPLAY.equals(command)) {
889             play();
890         } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
891             pause();
892             mPausedByTransientLossOfFocus = false;
893             seek(0);
894             releaseServiceUiAndStop();
895         } else if (REPEAT_ACTION.equals(action)) {
896             cycleRepeat();
897         } else if (SHUFFLE_ACTION.equals(action)) {
898             cycleShuffle();
899         } else if (CMDHEADSETHOOK.equals(command)) {
900             long timestamp = intent.getLongExtra(TIMESTAMP, 0);
901             handleHeadsetHookClick(timestamp);
902         }
903     }
904
905     private void handleHeadsetHookClick(long timestamp) {
906         if (mHeadsetHookWakeLock == null) {
907             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
908             mHeadsetHookWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
909                     "Eleven headset button");
910             mHeadsetHookWakeLock.setReferenceCounted(false);
911         }
912         // Make sure we don't indefinitely hold the wake lock under any circumstances
913         mHeadsetHookWakeLock.acquire(10000);
914
915         Message msg = mPlayerHandler.obtainMessage(HEADSET_HOOK_EVENT, Long.valueOf(timestamp));
916         msg.sendToTarget();
917     }
918
919     /**
920      * Updates the notification, considering the current play and activity state
921      */
922     private void updateNotification() {
923         final int newNotifyMode;
924         if (isPlaying()) {
925             newNotifyMode = NOTIFY_MODE_FOREGROUND;
926         } else if (recentlyPlayed()) {
927             newNotifyMode = NOTIFY_MODE_BACKGROUND;
928         } else {
929             newNotifyMode = NOTIFY_MODE_NONE;
930         }
931
932         int notificationId = hashCode();
933         if (mNotifyMode != newNotifyMode) {
934             if (mNotifyMode == NOTIFY_MODE_FOREGROUND) {
935                 stopForeground(newNotifyMode == NOTIFY_MODE_NONE);
936             } else if (newNotifyMode == NOTIFY_MODE_NONE) {
937                 mNotificationManager.cancel(notificationId);
938                 mNotificationPostTime = 0;
939             }
940         }
941
942         if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
943             startForeground(notificationId, buildNotification());
944         } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
945             mNotificationManager.notify(notificationId, buildNotification());
946         }
947
948         mNotifyMode = newNotifyMode;
949     }
950
951     private void cancelNotification() {
952         stopForeground(true);
953         mNotificationManager.cancel(hashCode());
954         mNotificationPostTime = 0;
955         mNotifyMode = NOTIFY_MODE_NONE;
956     }
957
958     /**
959      * @return A card ID used to save and restore playlists, i.e., the queue.
960      */
961     private int getCardId() {
962         final ContentResolver resolver = getContentResolver();
963         Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
964                 null, null);
965         int mCardId = -1;
966         if (cursor != null && cursor.moveToFirst()) {
967             mCardId = cursor.getInt(0);
968             cursor.close();
969             cursor = null;
970         }
971         return mCardId;
972     }
973
974     /**
975      * Called when we receive a ACTION_MEDIA_EJECT notification.
976      *
977      * @param storagePath The path to mount point for the removed media
978      */
979     public void closeExternalStorageFiles(final String storagePath) {
980         stop(true);
981         notifyChange(QUEUE_CHANGED);
982         notifyChange(META_CHANGED);
983     }
984
985     /**
986      * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
987      * intent will call closeExternalStorageFiles() if the external media is
988      * going to be ejected, so applications can clean up any files they have
989      * open.
990      */
991     public void registerExternalStorageListener() {
992         if (mUnmountReceiver == null) {
993             mUnmountReceiver = new BroadcastReceiver() {
994
995                 /**
996                  * {@inheritDoc}
997                  */
998                 @Override
999                 public void onReceive(final Context context, final Intent intent) {
1000                     final String action = intent.getAction();
1001                     if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
1002                         saveQueue(true);
1003                         mQueueIsSaveable = false;
1004                         closeExternalStorageFiles(intent.getData().getPath());
1005                     } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
1006                         mMediaMountedCount++;
1007                         mCardId = getCardId();
1008                         reloadQueue();
1009                         mQueueIsSaveable = true;
1010                         notifyChange(QUEUE_CHANGED);
1011                         notifyChange(META_CHANGED);
1012                     }
1013                 }
1014             };
1015             final IntentFilter filter = new IntentFilter();
1016             filter.addAction(Intent.ACTION_MEDIA_EJECT);
1017             filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
1018             filter.addDataScheme("file");
1019             registerReceiver(mUnmountReceiver, filter);
1020         }
1021     }
1022
1023     private void scheduleDelayedShutdown() {
1024         if (D) Log.v(TAG, "Scheduling shutdown in " + IDLE_DELAY + " ms");
1025         if (!mReadGranted) {
1026             return;
1027         }
1028         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
1029                 SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);
1030         mShutdownScheduled = true;
1031     }
1032
1033     private void cancelShutdown() {
1034         if (D) Log.d(TAG, "Cancelling delayed shutdown, scheduled = " + mShutdownScheduled);
1035         if (mShutdownScheduled) {
1036             mAlarmManager.cancel(mShutdownIntent);
1037             mShutdownScheduled = false;
1038         }
1039     }
1040
1041     /**
1042      * Stops playback
1043      *
1044      * @param goToIdle True to go to the idle state, false otherwise
1045      */
1046     private void stop(final boolean goToIdle) {
1047         if (D) Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
1048         if (mPlayer.isInitialized()) {
1049             mPlayer.stop();
1050         }
1051         mFileToPlay = null;
1052         closeCursor();
1053         if (goToIdle) {
1054             setIsSupposedToBePlaying(false, false);
1055         } else {
1056             stopForeground(false);
1057         }
1058     }
1059
1060     /**
1061      * Removes the range of tracks specified from the play list. If a file
1062      * within the range is the file currently being played, playback will move
1063      * to the next file after the range.
1064      *
1065      * @param first The first file to be removed
1066      * @param last The last file to be removed
1067      * @return the number of tracks deleted
1068      */
1069     private int removeTracksInternal(int first, int last) {
1070         synchronized (this) {
1071             if (last < first) {
1072                 return 0;
1073             } else if (first < 0) {
1074                 first = 0;
1075             } else if (last >= mPlaylist.size()) {
1076                 last = mPlaylist.size() - 1;
1077             }
1078
1079             boolean gotonext = false;
1080             if (first <= mPlayPos && mPlayPos <= last) {
1081                 mPlayPos = first;
1082                 gotonext = true;
1083             } else if (mPlayPos > last) {
1084                 mPlayPos -= last - first + 1;
1085             }
1086             final int numToRemove = last - first + 1;
1087
1088             if (first == 0 && last == mPlaylist.size() - 1) {
1089                 mPlayPos = -1;
1090                 mNextPlayPos = -1;
1091                 mPlaylist.clear();
1092                 mHistory.clear();
1093             } else {
1094                 for (int i = 0; i < numToRemove; i++) {
1095                     mPlaylist.remove(first);
1096                 }
1097
1098                 // remove the items from the history
1099                 // this is not ideal as the history shouldn't be impacted by this
1100                 // but since we are removing items from the array, it will throw
1101                 // an exception if we keep it around.  Idealistically with the queue
1102                 // rewrite this should be all be fixed
1103                 // https://cyanogen.atlassian.net/browse/MUSIC-44
1104                 ListIterator<Integer> positionIterator = mHistory.listIterator();
1105                 while (positionIterator.hasNext()) {
1106                     int pos = positionIterator.next();
1107                     if (pos >= first && pos <= last) {
1108                         positionIterator.remove();
1109                     } else if (pos > last) {
1110                         positionIterator.set(pos - numToRemove);
1111                     }
1112                 }
1113             }
1114             if (gotonext) {
1115                 if (mPlaylist.size() == 0) {
1116                     stop(true);
1117                     mPlayPos = -1;
1118                     closeCursor();
1119                 } else {
1120                     if (mShuffleMode != SHUFFLE_NONE) {
1121                         mPlayPos = getNextPosition(true);
1122                     } else if (mPlayPos >= mPlaylist.size()) {
1123                         mPlayPos = 0;
1124                     }
1125                     final boolean wasPlaying = isPlaying();
1126                     stop(false);
1127                     openCurrentAndNext();
1128                     if (wasPlaying) {
1129                         play();
1130                     }
1131                 }
1132                 notifyChange(META_CHANGED);
1133             }
1134             return last - first + 1;
1135         }
1136     }
1137
1138     /**
1139      * Adds a list to the playlist
1140      *
1141      * @param list The list to add
1142      * @param position The position to place the tracks
1143      */
1144     private void addToPlayList(final long[] list, int position, long sourceId, IdType sourceType) {
1145         final int addlen = list.length;
1146         if (position < 0) {
1147             mPlaylist.clear();
1148             position = 0;
1149         }
1150
1151         mPlaylist.ensureCapacity(mPlaylist.size() + addlen);
1152         if (position > mPlaylist.size()) {
1153             position = mPlaylist.size();
1154         }
1155
1156         final ArrayList<MusicPlaybackTrack> arrayList = new ArrayList<MusicPlaybackTrack>(addlen);
1157         for (int i = 0; i < list.length; i++) {
1158             arrayList.add(new MusicPlaybackTrack(list[i], sourceId, sourceType, i));
1159         }
1160
1161         mPlaylist.addAll(position, arrayList);
1162
1163         if (mPlaylist.size() == 0) {
1164             closeCursor();
1165             notifyChange(META_CHANGED);
1166         }
1167     }
1168
1169     /**
1170      * @param trackId The track ID
1171      */
1172     private void updateCursor(final long trackId) {
1173         updateCursor("_id=" + trackId, null);
1174     }
1175
1176     private void updateCursor(final String selection, final String[] selectionArgs) {
1177         synchronized (this) {
1178             closeCursor();
1179             mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1180                     PROJECTION, selection, selectionArgs);
1181         }
1182         updateAlbumCursor();
1183     }
1184
1185     private void updateCursor(final Uri uri) {
1186         synchronized (this) {
1187             closeCursor();
1188             mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);
1189         }
1190         updateAlbumCursor();
1191     }
1192
1193     private void updateAlbumCursor() {
1194         long albumId = getAlbumId();
1195         if (albumId >= 0) {
1196             mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
1197                     ALBUM_PROJECTION, "_id=" + albumId, null);
1198         } else {
1199             mAlbumCursor = null;
1200         }
1201     }
1202
1203     private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,
1204             String selection, String[] selectionArgs) {
1205         Cursor c = getContentResolver().query(uri, projection,
1206                 selection, selectionArgs, null, null);
1207         if (c == null) {
1208             return null;
1209         }
1210         if (!c.moveToFirst()) {
1211             c.close();
1212             return null;
1213         }
1214         return c;
1215      }
1216
1217     private synchronized void closeCursor() {
1218         if (mCursor != null) {
1219             mCursor.close();
1220             mCursor = null;
1221         }
1222         if (mAlbumCursor != null) {
1223             mAlbumCursor.close();
1224             mAlbumCursor = null;
1225         }
1226     }
1227
1228     /**
1229      * Called to open a new file as the current track and prepare the next for
1230      * playback
1231      */
1232     private void openCurrentAndNext() {
1233         openCurrentAndMaybeNext(true);
1234     }
1235
1236     /**
1237      * Called to open a new file as the current track and prepare the next for
1238      * playback
1239      *
1240      * @param openNext True to prepare the next track for playback, false
1241      *            otherwise.
1242      */
1243     private void openCurrentAndMaybeNext(final boolean openNext) {
1244         synchronized (this) {
1245             closeCursor();
1246
1247             if (mPlaylist.size() == 0) {
1248                 return;
1249             }
1250             stop(false);
1251
1252             boolean shutdown = false;
1253
1254             updateCursor(mPlaylist.get(mPlayPos).mId);
1255             while (true) {
1256                 if (mCursor != null
1257                         && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
1258                                 + mCursor.getLong(IDCOLIDX))) {
1259                     break;
1260                 }
1261
1262                 // if we get here then opening the file failed. We can close the
1263                 // cursor now, because
1264                 // we're either going to create a new one next, or stop trying
1265                 closeCursor();
1266                 if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {
1267                     final int pos = getNextPosition(false);
1268                     if (pos < 0) {
1269                         shutdown = true;
1270                         break;
1271                     }
1272                     mPlayPos = pos;
1273                     stop(false);
1274                     mPlayPos = pos;
1275                     updateCursor(mPlaylist.get(mPlayPos).mId);
1276                 } else {
1277                     mOpenFailedCounter = 0;
1278                     Log.w(TAG, "Failed to open file for playback");
1279                     shutdown = true;
1280                     break;
1281                 }
1282             }
1283
1284             if (shutdown) {
1285                 scheduleDelayedShutdown();
1286                 if (mIsSupposedToBePlaying) {
1287                     mIsSupposedToBePlaying = false;
1288                     notifyChange(PLAYSTATE_CHANGED);
1289                 }
1290             } else if (openNext) {
1291                 setNextTrack();
1292             }
1293         }
1294     }
1295
1296     private void sendErrorMessage(final String trackName) {
1297         final Intent i = new Intent(TRACK_ERROR);
1298         i.putExtra(TrackErrorExtra.TRACK_NAME, trackName);
1299         sendBroadcast(i);
1300     }
1301
1302     /**
1303      * @param force True to force the player onto the track next, false
1304      *            otherwise.
1305      * @param saveToHistory True to save the mPlayPos to the history
1306      * @return The next position to play.
1307      */
1308     private int getNextPosition(final boolean force) {
1309         // as a base case, if the playlist is empty just return -1
1310         if (mPlaylist == null || mPlaylist.isEmpty()) {
1311             return -1;
1312         }
1313         // if we're not forced to go to the next track and we are only playing the current track
1314         if (!force && mRepeatMode == REPEAT_CURRENT) {
1315             if (mPlayPos < 0) {
1316                 return 0;
1317             }
1318             return mPlayPos;
1319         } else if (mShuffleMode == SHUFFLE_NORMAL) {
1320             final int numTracks = mPlaylist.size();
1321
1322             // count the number of times a track has been played
1323             final int[] trackNumPlays = new int[numTracks];
1324             for (int i = 0; i < numTracks; i++) {
1325                 // set it all to 0
1326                 trackNumPlays[i] = 0;
1327             }
1328
1329             // walk through the history and add up the number of times the track
1330             // has been played
1331             final int numHistory = mHistory.size();
1332             for (int i = 0; i < numHistory; i++) {
1333                 final int idx = mHistory.get(i).intValue();
1334                 if (idx >= 0 && idx < numTracks) {
1335                     trackNumPlays[idx]++;
1336                 }
1337             }
1338
1339             // also add the currently playing track to the count
1340             if (mPlayPos >= 0 && mPlayPos < numTracks) {
1341                 trackNumPlays[mPlayPos]++;
1342             }
1343
1344             // figure out the least # of times a track has a played as well as
1345             // how many tracks share that count
1346             int minNumPlays = Integer.MAX_VALUE;
1347             int numTracksWithMinNumPlays = 0;
1348             for (int i = 0; i < trackNumPlays.length; i++) {
1349                 // if we found a new track that has less number of plays, reset the counters
1350                 if (trackNumPlays[i] < minNumPlays) {
1351                     minNumPlays = trackNumPlays[i];
1352                     numTracksWithMinNumPlays = 1;
1353                 } else if (trackNumPlays[i] == minNumPlays) {
1354                     // increment this track shares the # of tracks
1355                     numTracksWithMinNumPlays++;
1356                 }
1357             }
1358
1359             // if we've played each track at least once and all tracks have been played an equal
1360             // # of times and we aren't repeating all and we're not forcing a track, then
1361             // return no more tracks
1362             if (minNumPlays > 0 && numTracksWithMinNumPlays == numTracks
1363                     && mRepeatMode != REPEAT_ALL && !force) {
1364                     return -1;
1365             }
1366
1367             // else pick a track from the least number of played tracks
1368             int skip = mShuffler.nextInt(numTracksWithMinNumPlays);
1369             for (int i = 0; i < trackNumPlays.length; i++) {
1370                 if (trackNumPlays[i] == minNumPlays) {
1371                     if (skip == 0) {
1372                         return i;
1373                     } else {
1374                         skip--;
1375                     }
1376                 }
1377             }
1378
1379             // Unexpected to land here
1380             if (D) Log.e(TAG, "Getting the next position resulted did not get a result when it should have");
1381             return -1;
1382         } else if (mShuffleMode == SHUFFLE_AUTO) {
1383             doAutoShuffleUpdate();
1384             return mPlayPos + 1;
1385         } else {
1386             if (mPlayPos >= mPlaylist.size() - 1) {
1387                 if (mRepeatMode == REPEAT_NONE && !force) {
1388                     return -1;
1389                 } else if (mRepeatMode == REPEAT_ALL || force) {
1390                     return 0;
1391                 }
1392                 return -1;
1393             } else {
1394                 return mPlayPos + 1;
1395             }
1396         }
1397     }
1398
1399     /**
1400      * Sets the track to be played
1401      */
1402     private void setNextTrack() {
1403         setNextTrack(getNextPosition(false));
1404     }
1405
1406     /**
1407      * Sets the next track to be played
1408      * @param position the target position we want
1409      */
1410     private void setNextTrack(int position) {
1411         mNextPlayPos = position;
1412         if (D) Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
1413         if (mNextPlayPos >= 0 && mPlaylist != null && mNextPlayPos < mPlaylist.size()) {
1414             final long id = mPlaylist.get(mNextPlayPos).mId;
1415             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1416         } else {
1417             mPlayer.setNextDataSource(null);
1418         }
1419     }
1420
1421     /**
1422      * Creates a shuffled playlist used for party mode
1423      */
1424     private boolean makeAutoShuffleList() {
1425         Cursor cursor = null;
1426         try {
1427             cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1428                     new String[] {
1429                         MediaStore.Audio.Media._ID
1430                     }, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
1431             if (cursor == null || cursor.getCount() == 0) {
1432                 return false;
1433             }
1434             final int len = cursor.getCount();
1435             final long[] list = new long[len];
1436             for (int i = 0; i < len; i++) {
1437                 cursor.moveToNext();
1438                 list[i] = cursor.getLong(0);
1439             }
1440             mAutoShuffleList = list;
1441             return true;
1442         } catch (final RuntimeException e) {
1443         } finally {
1444             if (cursor != null) {
1445                 cursor.close();
1446                 cursor = null;
1447             }
1448         }
1449         return false;
1450     }
1451
1452     /**
1453      * Creates the party shuffle playlist
1454      */
1455     private void doAutoShuffleUpdate() {
1456         boolean notify = false;
1457         if (mPlayPos > 10) {
1458             removeTracks(0, mPlayPos - 9);
1459             notify = true;
1460         }
1461         final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));
1462         for (int i = 0; i < toAdd; i++) {
1463             int lookback = mHistory.size();
1464             int idx = -1;
1465             while (true) {
1466                 idx = mShuffler.nextInt(mAutoShuffleList.length);
1467                 if (!wasRecentlyUsed(idx, lookback)) {
1468                     break;
1469                 }
1470                 lookback /= 2;
1471             }
1472             mHistory.add(idx);
1473             if (mHistory.size() > MAX_HISTORY_SIZE) {
1474                 mHistory.remove(0);
1475             }
1476             mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, IdType.NA, -1));
1477             notify = true;
1478         }
1479         if (notify) {
1480             notifyChange(QUEUE_CHANGED);
1481         }
1482     }
1483
1484     /**/
1485     private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
1486         if (lookbacksize == 0) {
1487             return false;
1488         }
1489         final int histsize = mHistory.size();
1490         if (histsize < lookbacksize) {
1491             lookbacksize = histsize;
1492         }
1493         final int maxidx = histsize - 1;
1494         for (int i = 0; i < lookbacksize; i++) {
1495             final long entry = mHistory.get(maxidx - i);
1496             if (entry == idx) {
1497                 return true;
1498             }
1499         }
1500         return false;
1501     }
1502
1503     /**
1504      * Notify the change-receivers that something has changed.
1505      */
1506     private void notifyChange(final String what) {
1507         if (D) Log.d(TAG, "notifyChange: what = " + what);
1508
1509         // Update the lockscreen controls
1510         updateMediaSession(what);
1511
1512         if (what.equals(POSITION_CHANGED)) {
1513             return;
1514         }
1515
1516         final Intent intent = new Intent(what);
1517         intent.putExtra("id", getAudioId());
1518         intent.putExtra("artist", getArtistName());
1519         intent.putExtra("album", getAlbumName());
1520         intent.putExtra("track", getTrackName());
1521         intent.putExtra("playing", isPlaying());
1522
1523         if (NEW_LYRICS.equals(what)) {
1524             intent.putExtra("lyrics", mLyrics);
1525         }
1526
1527         sendStickyBroadcast(intent);
1528
1529         final Intent musicIntent = new Intent(intent);
1530         musicIntent.setAction(what.replace(ELEVEN_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
1531         sendStickyBroadcast(musicIntent);
1532
1533         if (what.equals(META_CHANGED)) {
1534             // Add the track to the recently played list.
1535             mRecentsCache.addSongId(getAudioId());
1536
1537             mSongPlayCountCache.bumpSongCount(getAudioId());
1538         } else if (what.equals(QUEUE_CHANGED)) {
1539             saveQueue(true);
1540             if (isPlaying()) {
1541                 // if we are in shuffle mode and our next track is still valid,
1542                 // try to re-use the track
1543                 // We need to reimplement the queue to prevent hacky solutions like this
1544                 // https://cyanogen.atlassian.net/browse/MUSIC-175
1545                 // https://cyanogen.atlassian.net/browse/MUSIC-44
1546                 if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size()
1547                         && getShuffleMode() != SHUFFLE_NONE) {
1548                     setNextTrack(mNextPlayPos);
1549                 } else {
1550                     setNextTrack();
1551                 }
1552             }
1553         } else {
1554             saveQueue(false);
1555         }
1556
1557         if (what.equals(PLAYSTATE_CHANGED)) {
1558             updateNotification();
1559         }
1560
1561         // Update the app-widgets
1562         mAppWidgetSmall.notifyChange(this, what);
1563         mAppWidgetLarge.notifyChange(this, what);
1564         mAppWidgetLargeAlternate.notifyChange(this, what);
1565     }
1566
1567     private void updateMediaSession(final String what) {
1568         int playState = mIsSupposedToBePlaying
1569                 ? PlaybackState.STATE_PLAYING
1570                 : PlaybackState.STATE_PAUSED;
1571
1572         long playBackStateActions = PlaybackState.ACTION_PLAY |
1573                 PlaybackState.ACTION_PLAY_PAUSE |
1574                 PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
1575                 PlaybackState.ACTION_PAUSE |
1576                 PlaybackState.ACTION_SKIP_TO_NEXT |
1577                 PlaybackState.ACTION_SKIP_TO_PREVIOUS |
1578                 PlaybackState.ACTION_STOP;
1579
1580         if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
1581             mSession.setPlaybackState(new PlaybackState.Builder()
1582                     .setActions(playBackStateActions)
1583                     .setActiveQueueItemId(getAudioId())
1584                     .setState(playState, position(), 1.0f).build());
1585         } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
1586             Bitmap albumArt = getAlbumArt(false).getBitmap();
1587             if (albumArt != null) {
1588                 // RemoteControlClient wants to recycle the bitmaps thrown at it, so we need
1589                 // to make sure not to hand out our cache copy
1590                 Bitmap.Config config = albumArt.getConfig();
1591                 if (config == null) {
1592                     config = Bitmap.Config.ARGB_8888;
1593                 }
1594                 albumArt = albumArt.copy(config, false);
1595             }
1596
1597             mSession.setMetadata(new MediaMetadata.Builder()
1598                     .putString(MediaMetadata.METADATA_KEY_ARTIST, getArtistName())
1599                     .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
1600                     .putString(MediaMetadata.METADATA_KEY_ALBUM, getAlbumName())
1601                     .putString(MediaMetadata.METADATA_KEY_TITLE, getTrackName())
1602                     .putLong(MediaMetadata.METADATA_KEY_DURATION, duration())
1603                     .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)
1604                     .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, getQueue().length)
1605                     .putString(MediaMetadata.METADATA_KEY_GENRE, getGenreName())
1606                     .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART,
1607                             mShowAlbumArtOnLockscreen ? albumArt : null)
1608                     .build());
1609
1610             if (what.equals(QUEUE_CHANGED)) {
1611                 updateMediaSessionQueue();
1612             }
1613
1614             mSession.setPlaybackState(new PlaybackState.Builder()
1615                     .setActions(playBackStateActions)
1616                     .setActiveQueueItemId(getAudioId())
1617                     .setState(playState, position(), 1.0f).build());
1618         }
1619     }
1620
1621     private synchronized void updateMediaSessionQueue() {
1622         if (mQueueUpdateTask != null) {
1623             mQueueUpdateTask.cancel(true);
1624         }
1625         mQueueUpdateTask = new QueueUpdateTask(getQueue());
1626         mQueueUpdateTask.execute();
1627     }
1628
1629     private Notification buildNotification() {
1630         final String albumName = getAlbumName();
1631         final String artistName = getArtistName();
1632         final boolean isPlaying = isPlaying();
1633         String text = TextUtils.isEmpty(albumName)
1634                 ? artistName : artistName + " - " + albumName;
1635
1636         int playButtonResId = isPlaying
1637                 ? R.drawable.btn_playback_pause : R.drawable.btn_playback_play;
1638         int playButtonTitleResId = isPlaying
1639                 ? R.string.accessibility_pause : R.string.accessibility_play;
1640
1641         Notification.MediaStyle style = new Notification.MediaStyle()
1642                 .setMediaSession(mSession.getSessionToken())
1643                 .setShowActionsInCompactView(0, 1, 2);
1644
1645         Intent nowPlayingIntent = new Intent("org.lineageos.eleven.AUDIO_PLAYER")
1646                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1647         PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, 0);
1648         BitmapWithColors artwork = getAlbumArt(false);
1649
1650         if (mNotificationPostTime == 0) {
1651             mNotificationPostTime = System.currentTimeMillis();
1652         }
1653
1654         Notification.Builder builder = new Notification.Builder(this)
1655                 .setSmallIcon(R.drawable.ic_notification)
1656                 .setLargeIcon(artwork.getBitmap())
1657                 .setContentIntent(clickIntent)
1658                 .setContentTitle(getTrackName())
1659                 .setContentText(text)
1660                 .setWhen(mNotificationPostTime)
1661                 .setShowWhen(false)
1662                 .setStyle(style)
1663                 .setVisibility(Notification.VISIBILITY_PUBLIC)
1664                 .addAction(R.drawable.btn_playback_previous,
1665                         getString(R.string.accessibility_prev),
1666                         retrievePlaybackAction(PREVIOUS_ACTION))
1667                 .addAction(playButtonResId, getString(playButtonTitleResId),
1668                         retrievePlaybackAction(TOGGLEPAUSE_ACTION))
1669                 .addAction(R.drawable.btn_playback_next,
1670                         getString(R.string.accessibility_next),
1671                         retrievePlaybackAction(NEXT_ACTION));
1672
1673         builder.setColor(artwork.getVibrantDarkColor());
1674
1675         return builder.build();
1676     }
1677
1678     private final PendingIntent retrievePlaybackAction(final String action) {
1679         final ComponentName serviceName = new ComponentName(this, MusicPlaybackService.class);
1680         Intent intent = new Intent(action);
1681         intent.setComponent(serviceName);
1682
1683         return PendingIntent.getService(this, 0, intent, 0);
1684     }
1685
1686     /**
1687      * Saves the queue
1688      *
1689      * @param full True if the queue is full
1690      */
1691     private void saveQueue(final boolean full) {
1692         if (!mQueueIsSaveable || mPreferences == null) {
1693             return;
1694         }
1695
1696         final SharedPreferences.Editor editor = mPreferences.edit();
1697         if (full) {
1698             mPlaybackStateStore.saveState(mPlaylist,
1699                     mShuffleMode != SHUFFLE_NONE ? mHistory : null);
1700             editor.putInt("cardid", mCardId);
1701         }
1702         editor.putInt("curpos", mPlayPos);
1703         if (mPlayer.isInitialized()) {
1704             editor.putLong("seekpos", mPlayer.position());
1705         }
1706         editor.putInt("repeatmode", mRepeatMode);
1707         editor.putInt("shufflemode", mShuffleMode);
1708         editor.apply();
1709     }
1710
1711     /**
1712      * Reloads the queue as the user left it the last time they stopped using
1713      * Apollo
1714      */
1715     private void reloadQueue() {
1716         int id = mCardId;
1717         if (mPreferences.contains("cardid")) {
1718             id = mPreferences.getInt("cardid", ~mCardId);
1719         }
1720         if (id == mCardId) {
1721             mPlaylist = mPlaybackStateStore.getQueue();
1722         }
1723         if (mPlaylist.size() > 0) {
1724             final int pos = mPreferences.getInt("curpos", 0);
1725             if (pos < 0 || pos >= mPlaylist.size()) {
1726                 mPlaylist.clear();
1727                 return;
1728             }
1729             mPlayPos = pos;
1730             updateCursor(mPlaylist.get(mPlayPos).mId);
1731             if (mCursor == null) {
1732                 SystemClock.sleep(3000);
1733                 updateCursor(mPlaylist.get(mPlayPos).mId);
1734             }
1735             synchronized (this) {
1736                 closeCursor();
1737                 mOpenFailedCounter = 20;
1738                 openCurrentAndNext();
1739             }
1740             if (!mPlayer.isInitialized()) {
1741                 mPlaylist.clear();
1742                 return;
1743             }
1744
1745             final long seekpos = mPreferences.getLong("seekpos", 0);
1746             seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
1747
1748             if (D) {
1749                 Log.d(TAG, "restored queue, currently at position "
1750                         + position() + "/" + duration()
1751                         + " (requested " + seekpos + ")");
1752             }
1753
1754             int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
1755             if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
1756                 repmode = REPEAT_NONE;
1757             }
1758             mRepeatMode = repmode;
1759
1760             int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
1761             if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
1762                 shufmode = SHUFFLE_NONE;
1763             }
1764             if (shufmode != SHUFFLE_NONE) {
1765                 mHistory = mPlaybackStateStore.getHistory(mPlaylist.size());
1766             }
1767             if (shufmode == SHUFFLE_AUTO) {
1768                 if (!makeAutoShuffleList()) {
1769                     shufmode = SHUFFLE_NONE;
1770                 }
1771             }
1772             mShuffleMode = shufmode;
1773         }
1774     }
1775
1776     /**
1777      * Opens a file and prepares it for playback
1778      *
1779      * @param path The path of the file to open
1780      */
1781     public boolean openFile(final String path) {
1782         if (D) Log.d(TAG, "openFile: path = " + path);
1783         synchronized (this) {
1784             if (path == null) {
1785                 return false;
1786             }
1787
1788             // If mCursor is null, try to associate path with a database cursor
1789             if (mCursor == null) {
1790                 Uri uri = Uri.parse(path);
1791                 boolean shouldAddToPlaylist = true;     // should try adding audio info to playlist
1792                 long id = -1;
1793                 try {
1794                     id = Long.valueOf(uri.getLastPathSegment());
1795                 } catch (NumberFormatException ex) {
1796                     // Ignore
1797                 }
1798
1799                 if (id != -1 && path.startsWith(
1800                                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
1801                     updateCursor(uri);
1802
1803                 } else if (id != -1 && path.startsWith(
1804                                     MediaStore.Files.getContentUri("external").toString())) {
1805                     updateCursor(id);
1806
1807                 // handle downloaded media files
1808                 } else if ( path.startsWith("content://downloads/") ) {
1809
1810                     // extract MediaProvider(MP) uri , if available
1811                     // Downloads.Impl.COLUMN_MEDIAPROVIDER_URI
1812                     String mpUri = getValueForDownloadedFile(this, uri, "mediaprovider_uri");
1813                     if (D) Log.i(TAG, "Downloaded file's MP uri : " + mpUri);
1814                     if ( !TextUtils.isEmpty(mpUri) ) {
1815                         // if mpUri is valid, play that URI instead
1816                         if (openFile(mpUri)) {
1817                             // notify impending change in track
1818                             notifyChange(META_CHANGED);
1819                             return true;
1820                         } else {
1821                             return false;
1822                         }
1823                     } else {
1824                         // create phantom cursor with download info, if a MP uri wasn't found
1825                         updateCursorForDownloadedFile(this, uri);
1826                         shouldAddToPlaylist = false;    // song info isn't available in MediaStore
1827                     }
1828
1829                 } else {
1830                     // assuming a "file://" uri by this point ...
1831                     String where = MediaStore.Audio.Media.DATA + "=?";
1832                     String[] selectionArgs = new String[]{path};
1833                     updateCursor(where, selectionArgs);
1834                 }
1835                 try {
1836                     if (mCursor != null && shouldAddToPlaylist) {
1837                         mPlaylist.clear();
1838                         mPlaylist.add(new MusicPlaybackTrack(
1839                                                 mCursor.getLong(IDCOLIDX), -1, IdType.NA, -1));
1840                         // propagate the change in playlist state
1841                         notifyChange(QUEUE_CHANGED);
1842                         mPlayPos = 0;
1843                         mHistory.clear();
1844                     }
1845                 } catch (final UnsupportedOperationException ex) {
1846                     // Ignore
1847                 }
1848             }
1849
1850             mFileToPlay = path;
1851             mPlayer.setDataSource(mFileToPlay);
1852             if (mPlayer.isInitialized()) {
1853                 mOpenFailedCounter = 0;
1854                 return true;
1855             }
1856
1857             String trackName = getTrackName();
1858             if (TextUtils.isEmpty(trackName)) {
1859                 trackName = path;
1860             }
1861             sendErrorMessage(trackName);
1862
1863             stop(true);
1864             return false;
1865         }
1866     }
1867
1868     /*
1869         Columns for a pseudo cursor we are creating for downloaded songs
1870         Modeled after mCursor to be able to respond to respond to the same queries as it
1871      */
1872     private static final String[] PROJECTION_MATRIX = new String[] {
1873             "_id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
1874             MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
1875             MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
1876             MediaStore.Audio.Media.ARTIST_ID
1877     };
1878
1879     /**
1880      * Creates a pseudo cursor for downloaded audio files with minimal info
1881      * @param context needed to query the download uri
1882      * @param uri the uri of the downloaded file
1883      */
1884     private void updateCursorForDownloadedFile(Context context, Uri uri) {
1885         synchronized (this) {
1886             closeCursor();  // clear mCursor
1887             MatrixCursor cursor = new MatrixCursor(PROJECTION_MATRIX);
1888             // get title of the downloaded file ; Downloads.Impl.COLUMN_TITLE
1889             String title = getValueForDownloadedFile(this, uri, "title" );
1890             // populating the cursor with bare minimum info
1891             cursor.addRow(new Object[] {
1892                     null ,
1893                     null ,
1894                     null ,
1895                     title ,
1896                     null ,
1897                     null ,
1898                     null ,
1899                     null
1900             });
1901             mCursor = cursor;
1902             mCursor.moveToFirst();
1903         }
1904     }
1905
1906     /**
1907      * Query the DownloadProvider to get the value in the specified column
1908      * @param context
1909      * @param uri the uri of the downloaded file
1910      * @param column
1911      * @return
1912      */
1913     private String getValueForDownloadedFile(Context context, Uri uri, String column) {
1914
1915         Cursor cursor = null;
1916         final String[] projection = {
1917                 column
1918         };
1919
1920         try {
1921             cursor = context.getContentResolver().query(uri, projection, null, null, null);
1922             if (cursor != null && cursor.moveToFirst()) {
1923                 return cursor.getString(0);
1924             }
1925         } finally {
1926             if (cursor != null) {
1927                 cursor.close();
1928             }
1929         }
1930         return null;
1931     }
1932
1933     /**
1934      * Returns the audio session ID
1935      *
1936      * @return The current media player audio session ID
1937      */
1938     public int getAudioSessionId() {
1939         synchronized (this) {
1940             return mPlayer.getAudioSessionId();
1941         }
1942     }
1943
1944     /**
1945      * Indicates if the media storeage device has been mounted or not
1946      *
1947      * @return 1 if Intent.ACTION_MEDIA_MOUNTED is called, 0 otherwise
1948      */
1949     public int getMediaMountedCount() {
1950         return mMediaMountedCount;
1951     }
1952
1953     /**
1954      * Returns the shuffle mode
1955      *
1956      * @return The current shuffle mode (all, party, none)
1957      */
1958     public int getShuffleMode() {
1959         return mShuffleMode;
1960     }
1961
1962     /**
1963      * Returns the repeat mode
1964      *
1965      * @return The current repeat mode (all, one, none)
1966      */
1967     public int getRepeatMode() {
1968         return mRepeatMode;
1969     }
1970
1971     /**
1972      * Removes all instances of the track with the given ID from the playlist.
1973      *
1974      * @param id The id to be removed
1975      * @return how many instances of the track were removed
1976      */
1977     public int removeTrack(final long id) {
1978         int numremoved = 0;
1979         synchronized (this) {
1980             for (int i = 0; i < mPlaylist.size(); i++) {
1981                 if (mPlaylist.get(i).mId == id) {
1982                     numremoved += removeTracksInternal(i, i);
1983                     i--;
1984                 }
1985             }
1986         }
1987         if (numremoved > 0) {
1988             notifyChange(QUEUE_CHANGED);
1989         }
1990         return numremoved;
1991     }
1992
1993     /**
1994      * Removes a song from the playlist at the specified position.
1995      *
1996      * @param id The song id to be removed
1997      * @param position The position of the song in the playlist
1998      * @return true if successful
1999      */
2000     public boolean removeTrackAtPosition(final long id, final int position) {
2001         synchronized (this) {
2002             if (    position >=0 &&
2003                     position < mPlaylist.size() &&
2004                     mPlaylist.get(position).mId == id  ) {
2005
2006                 return removeTracks(position, position) > 0;
2007             }
2008         }
2009         return false;
2010     }
2011
2012     /**
2013      * Removes the range of tracks specified from the play list. If a file
2014      * within the range is the file currently being played, playback will move
2015      * to the next file after the range.
2016      *
2017      * @param first The first file to be removed
2018      * @param last The last file to be removed
2019      * @return the number of tracks deleted
2020      */
2021     public int removeTracks(final int first, final int last) {
2022         final int numremoved = removeTracksInternal(first, last);
2023         if (numremoved > 0) {
2024             notifyChange(QUEUE_CHANGED);
2025         }
2026         return numremoved;
2027     }
2028
2029     /**
2030      * Returns the position in the queue
2031      *
2032      * @return the current position in the queue
2033      */
2034     public int getQueuePosition() {
2035         synchronized (this) {
2036             return mPlayPos;
2037         }
2038     }
2039
2040     /**
2041      * @return the size of the queue history cache
2042      */
2043     public int getQueueHistorySize() {
2044         synchronized (this) {
2045             return mHistory.size();
2046         }
2047     }
2048
2049     /**
2050      * @return the position in the history
2051      */
2052     public int getQueueHistoryPosition(int position) {
2053         synchronized (this) {
2054             if (position >= 0 && position < mHistory.size()) {
2055                 return mHistory.get(position);
2056             }
2057         }
2058
2059         return -1;
2060     }
2061
2062     /**
2063      * @return the queue of history positions
2064      */
2065     public int[] getQueueHistoryList() {
2066         synchronized (this) {
2067             int[] history = new int[mHistory.size()];
2068             for (int i = 0; i < mHistory.size(); i++) {
2069                 history[i] = mHistory.get(i);
2070             }
2071
2072             return history;
2073         }
2074     }
2075
2076     /**
2077      * Returns the path to current song
2078      *
2079      * @return The path to the current song
2080      */
2081     public String getPath() {
2082         synchronized (this) {
2083             if (mCursor == null) {
2084                 return null;
2085             }
2086             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
2087         }
2088     }
2089
2090     /**
2091      * Returns the album name
2092      *
2093      * @return The current song album Name
2094      */
2095     public String getAlbumName() {
2096         synchronized (this) {
2097             if (mCursor == null) {
2098                 return null;
2099             }
2100             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
2101         }
2102     }
2103
2104     /**
2105      * Returns the song name
2106      *
2107      * @return The current song name
2108      */
2109     public String getTrackName() {
2110         synchronized (this) {
2111             if (mCursor == null) {
2112                 return null;
2113             }
2114             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
2115         }
2116     }
2117
2118     /**
2119      * Returns the genre name of song
2120      *
2121      * @return The current song genre name
2122      */
2123     public String getGenreName() {
2124         synchronized (this) {
2125             if (mCursor == null || mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
2126                 return null;
2127             }
2128             String[] genreProjection = { MediaStore.Audio.Genres.NAME };
2129             Uri genreUri = MediaStore.Audio.Genres.getContentUriForAudioId("external",
2130                     (int) mPlaylist.get(mPlayPos).mId);
2131             Cursor genreCursor = getContentResolver().query(genreUri, genreProjection,
2132                     null, null, null);
2133             if (genreCursor != null) {
2134                 try {
2135                     if (genreCursor.moveToFirst()) {
2136                         return genreCursor.getString(
2137                             genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME));
2138                     }
2139                 } finally {
2140                     genreCursor.close();
2141                 }
2142             }
2143             return null;
2144         }
2145     }
2146
2147     /**
2148      * Returns the artist name
2149      *
2150      * @return The current song artist name
2151      */
2152     public String getArtistName() {
2153         synchronized (this) {
2154             if (mCursor == null) {
2155                 return null;
2156             }
2157             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));
2158         }
2159     }
2160
2161     /**
2162      * Returns the artist name
2163      *
2164      * @return The current song artist name
2165      */
2166     public String getAlbumArtistName() {
2167         synchronized (this) {
2168             if (mAlbumCursor == null) {
2169                 return null;
2170             }
2171             return mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(AlbumColumns.ARTIST));
2172         }
2173     }
2174
2175     /**
2176      * Returns the album ID
2177      *
2178      * @return The current song album ID
2179      */
2180     public long getAlbumId() {
2181         synchronized (this) {
2182             if (mCursor == null) {
2183                 return -1;
2184             }
2185             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
2186         }
2187     }
2188
2189     /**
2190      * Returns the artist ID
2191      *
2192      * @return The current song artist ID
2193      */
2194     public long getArtistId() {
2195         synchronized (this) {
2196             if (mCursor == null) {
2197                 return -1;
2198             }
2199             return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));
2200         }
2201     }
2202
2203     /**
2204      * @return The audio id of the track
2205      */
2206     public long getAudioId() {
2207         MusicPlaybackTrack track = getCurrentTrack();
2208         if (track != null) {
2209             return track.mId;
2210         }
2211
2212         return -1;
2213     }
2214
2215     /**
2216      * Gets the currently playing music track
2217      */
2218     public MusicPlaybackTrack getCurrentTrack() {
2219         return getTrack(mPlayPos);
2220     }
2221
2222     /**
2223      * Gets the music track from the queue at the specified index
2224      * @param index position
2225      * @return music track or null
2226      */
2227     public synchronized MusicPlaybackTrack getTrack(int index) {
2228         if (index >= 0 && index < mPlaylist.size() && mPlayer.isInitialized()) {
2229             return mPlaylist.get(index);
2230         }
2231
2232         return null;
2233     }
2234
2235     /**
2236      * Returns the next audio ID
2237      *
2238      * @return The next track ID
2239      */
2240     public long getNextAudioId() {
2241         synchronized (this) {
2242             if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && mPlayer.isInitialized()) {
2243                 return mPlaylist.get(mNextPlayPos).mId;
2244             }
2245         }
2246         return -1;
2247     }
2248
2249     /**
2250      * Returns the previous audio ID
2251      *
2252      * @return The previous track ID
2253      */
2254     public long getPreviousAudioId() {
2255         synchronized (this) {
2256             if (mPlayer.isInitialized()) {
2257                 int pos = getPreviousPlayPosition(false);
2258                 if (pos >= 0 && pos < mPlaylist.size()) {
2259                     return mPlaylist.get(pos).mId;
2260                 }
2261             }
2262         }
2263         return -1;
2264     }
2265
2266     /**
2267      * Seeks the current track to a specific time
2268      *
2269      * @param position The time to seek to
2270      * @return The time to play the track at
2271      */
2272     public long seek(long position) {
2273         if (mPlayer.isInitialized()) {
2274             if (position < 0) {
2275                 position = 0;
2276             } else if (position > mPlayer.duration()) {
2277                 position = mPlayer.duration();
2278             }
2279             long result = mPlayer.seek(position);
2280             notifyChange(POSITION_CHANGED);
2281             return result;
2282         }
2283         return -1;
2284     }
2285
2286     /**
2287      * Seeks the current track to a position relative to its current position
2288      * If the relative position is after or before the track, it will also automatically
2289      * jump to the previous or next track respectively
2290      *
2291      * @param deltaInMs The delta time to seek to in milliseconds
2292      */
2293     public void seekRelative(long deltaInMs) {
2294         synchronized (this) {
2295             if (mPlayer.isInitialized()) {
2296                 final long newPos = position() + deltaInMs;
2297                 final long duration = duration();
2298                 if (newPos < 0) {
2299                     prev(true);
2300                     // seek to the new duration + the leftover position
2301                     seek(duration() + newPos);
2302                 } else if (newPos >= duration) {
2303                     gotoNext(true);
2304                     // seek to the leftover duration
2305                     seek(newPos - duration);
2306                 } else {
2307                     seek(newPos);
2308                 }
2309             }
2310         }
2311     }
2312
2313     /**
2314      * Returns the current position in time of the currenttrack
2315      *
2316      * @return The current playback position in miliseconds
2317      */
2318     public long position() {
2319         if (mPlayer.isInitialized()) {
2320             return mPlayer.position();
2321         }
2322         return -1;
2323     }
2324
2325     /**
2326      * Returns the full duration of the current track
2327      *
2328      * @return The duration of the current track in miliseconds
2329      */
2330     public long duration() {
2331         if (mPlayer.isInitialized()) {
2332             return mPlayer.duration();
2333         }
2334         return -1;
2335     }
2336
2337     /**
2338      * Returns the queue
2339      *
2340      * @return The queue as a long[]
2341      */
2342     public long[] getQueue() {
2343         synchronized (this) {
2344             final int len = mPlaylist.size();
2345             final long[] list = new long[len];
2346             for (int i = 0; i < len; i++) {
2347                 list[i] = mPlaylist.get(i).mId;
2348             }
2349             return list;
2350         }
2351     }
2352
2353     /**
2354      * Gets the track id at a given position in the queue
2355      * @param position
2356      * @return track id in the queue position
2357      */
2358     public long getQueueItemAtPosition(int position) {
2359         synchronized (this) {
2360             if (position >= 0 && position < mPlaylist.size()) {
2361                 return mPlaylist.get(position).mId;
2362             }
2363         }
2364
2365         return -1;
2366     }
2367
2368     /**
2369      * @return the size of the queue
2370      */
2371     public int getQueueSize() {
2372         synchronized (this) {
2373             return mPlaylist.size();
2374         }
2375     }
2376
2377     /**
2378      * @return True if music is playing, false otherwise
2379      */
2380     public boolean isPlaying() {
2381         return mIsSupposedToBePlaying;
2382     }
2383
2384     /**
2385      * Helper function to wrap the logic around mIsSupposedToBePlaying for consistentcy
2386      * @param value to set mIsSupposedToBePlaying to
2387      * @param notify whether we want to fire PLAYSTATE_CHANGED event
2388      */
2389     private void setIsSupposedToBePlaying(boolean value, boolean notify) {
2390         if (mIsSupposedToBePlaying != value) {
2391             mIsSupposedToBePlaying = value;
2392
2393             // Update mLastPlayed time first and notify afterwards, as
2394             // the notification listener method needs the up-to-date value
2395             // for the recentlyPlayed() method to work
2396             if (!mIsSupposedToBePlaying) {
2397                 scheduleDelayedShutdown();
2398                 mLastPlayedTime = System.currentTimeMillis();
2399             }
2400
2401             if (notify) {
2402                 notifyChange(PLAYSTATE_CHANGED);
2403             }
2404         }
2405     }
2406
2407     /**
2408      * @return true if is playing or has played within the last IDLE_DELAY time
2409      */
2410     private boolean recentlyPlayed() {
2411         return isPlaying() || System.currentTimeMillis() - mLastPlayedTime < IDLE_DELAY;
2412     }
2413
2414     /**
2415      * Opens a list for playback
2416      *
2417      * @param list The list of tracks to open
2418      * @param position The position to start playback at
2419      */
2420     public void open(final long[] list, final int position, long sourceId, IdType sourceType) {
2421         synchronized (this) {
2422             if (mShuffleMode == SHUFFLE_AUTO) {
2423                 mShuffleMode = SHUFFLE_NORMAL;
2424             }
2425             final long oldId = getAudioId();
2426             final int listlength = list.length;
2427             boolean newlist = true;
2428             if (mPlaylist.size() == listlength) {
2429                 newlist = false;
2430                 for (int i = 0; i < listlength; i++) {
2431                     if (list[i] != mPlaylist.get(i).mId) {
2432                         newlist = true;
2433                         break;
2434                     }
2435                 }
2436             }
2437             if (newlist) {
2438                 addToPlayList(list, -1, sourceId, sourceType);
2439                 notifyChange(QUEUE_CHANGED);
2440             }
2441             if (position >= 0) {
2442                 mPlayPos = position;
2443             } else {
2444                 mPlayPos = mShuffler.nextInt(mPlaylist.size());
2445             }
2446             mHistory.clear();
2447             openCurrentAndNext();
2448             if (oldId != getAudioId()) {
2449                 notifyChange(META_CHANGED);
2450             }
2451         }
2452     }
2453
2454     /**
2455      * Stops playback.
2456      */
2457     public void stop() {
2458         stopShakeDetector(false);
2459         stop(true);
2460     }
2461
2462     /**
2463      * Resumes or starts playback.
2464      */
2465     public void play() {
2466         startShakeDetector();
2467         play(true);
2468     }
2469
2470     /**
2471      * Resumes or starts playback.
2472      * @param createNewNextTrack True if you want to figure out the next track, false
2473      *                           if you want to re-use the existing next track (used for going back)
2474      */
2475     public void play(boolean createNewNextTrack) {
2476         int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
2477                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
2478
2479         if (D) Log.d(TAG, "Starting playback: audio focus request status = " + status);
2480
2481         if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
2482             return;
2483         }
2484
2485         final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2486         intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2487         intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
2488         sendBroadcast(intent);
2489
2490         mSession.setActive(true);
2491
2492         if (createNewNextTrack) {
2493             setNextTrack();
2494         } else {
2495             setNextTrack(mNextPlayPos);
2496         }
2497
2498         if (mPlayer.isInitialized()) {
2499             final long duration = mPlayer.duration();
2500             if (mRepeatMode != REPEAT_CURRENT && duration > 2000
2501                     && mPlayer.position() >= duration - 2000) {
2502                 gotoNext(true);
2503             }
2504
2505             mPlayer.start();
2506             mPlayerHandler.removeMessages(FADEDOWN);
2507             mPlayerHandler.sendEmptyMessage(FADEUP);
2508
2509             setIsSupposedToBePlaying(true, true);
2510
2511             cancelShutdown();
2512             updateNotification();
2513         } else if (mPlaylist.size() <= 0) {
2514             setShuffleMode(SHUFFLE_AUTO);
2515         }
2516     }
2517
2518     private void togglePlayPause() {
2519         if (isPlaying()) {
2520             pause();
2521             mPausedByTransientLossOfFocus = false;
2522         } else {
2523             play();
2524         }
2525     }
2526
2527     /**
2528      * Temporarily pauses playback.
2529      */
2530     public void pause() {
2531         if (mPlayerHandler == null) return;
2532         if (D) Log.d(TAG, "Pausing playback");
2533         synchronized (this) {
2534             if (mPlayerHandler != null) {
2535                 mPlayerHandler.removeMessages(FADEUP);
2536             }
2537             if (mIsSupposedToBePlaying) {
2538                 final Intent intent = new Intent(
2539                         AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
2540                 intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2541                 intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
2542                 sendBroadcast(intent);
2543
2544                 if (mPlayer != null) {
2545                     mPlayer.pause();
2546                 }
2547                 setIsSupposedToBePlaying(false, true);
2548                 stopShakeDetector(false);
2549             }
2550         }
2551     }
2552
2553     /**
2554      * Changes from the current track to the next track
2555      */
2556     public void gotoNext(final boolean force) {
2557         if (D) Log.d(TAG, "Going to next track");
2558         synchronized (this) {
2559             if (mPlaylist.size() <= 0) {
2560                 if (D) Log.d(TAG, "No play queue");
2561                 scheduleDelayedShutdown();
2562                 return;
2563             }
2564             int pos = mNextPlayPos;
2565             if (pos < 0) {
2566                 pos = getNextPosition(force);
2567             }
2568
2569             if (pos < 0) {
2570                 setIsSupposedToBePlaying(false, true);
2571                 return;
2572             }
2573
2574             stop(false);
2575             setAndRecordPlayPos(pos);
2576             openCurrentAndNext();
2577             play();
2578             notifyChange(META_CHANGED);
2579         }
2580     }
2581
2582     public void setAndRecordPlayPos(int nextPos) {
2583         synchronized (this) {
2584             // save to the history
2585             if (mShuffleMode != SHUFFLE_NONE) {
2586                 mHistory.add(mPlayPos);
2587                 if (mHistory.size() > MAX_HISTORY_SIZE) {
2588                     mHistory.remove(0);
2589                 }
2590             }
2591
2592             mPlayPos = nextPos;
2593         }
2594     }
2595
2596     /**
2597      * Changes from the current track to the previous played track
2598      */
2599     public void prev(boolean forcePrevious) {
2600         synchronized (this) {
2601             // if we aren't repeating 1, and we are either early in the song
2602             // or we want to force go back, then go to the prevous track
2603             boolean goPrevious = getRepeatMode() != REPEAT_CURRENT &&
2604                     (position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);
2605
2606             if (goPrevious) {
2607                 if (D) Log.d(TAG, "Going to previous track");
2608                 int pos = getPreviousPlayPosition(true);
2609                 // if we have no more previous tracks, quit
2610                 if (pos < 0) {
2611                     return;
2612                 }
2613                 mNextPlayPos = mPlayPos;
2614                 mPlayPos = pos;
2615                 stop(false);
2616                 openCurrent();
2617                 play(false);
2618                 notifyChange(META_CHANGED);
2619             } else {
2620                 if (D) Log.d(TAG, "Going to beginning of track");
2621                 seek(0);
2622                 play(false);
2623             }
2624         }
2625     }
2626
2627     public int getPreviousPlayPosition(boolean removeFromHistory) {
2628         synchronized (this) {
2629             if (mShuffleMode == SHUFFLE_NORMAL) {
2630                 // Go to previously-played track and remove it from the history
2631                 final int histsize = mHistory.size();
2632                 if (histsize == 0) {
2633                     return -1;
2634                 }
2635                 final Integer pos = mHistory.get(histsize - 1);
2636                 if (removeFromHistory) {
2637                     mHistory.remove(histsize - 1);
2638                 }
2639                 return pos.intValue();
2640             } else {
2641                 if (mPlayPos > 0) {
2642                     return mPlayPos - 1;
2643                 } else {
2644                     return mPlaylist.size() - 1;
2645                 }
2646             }
2647         }
2648     }
2649
2650     /**
2651      * We don't want to open the current and next track when the user is using
2652      * the {@code #prev()} method because they won't be able to travel back to
2653      * the previously listened track if they're shuffling.
2654      */
2655     private void openCurrent() {
2656         openCurrentAndMaybeNext(false);
2657     }
2658
2659     /**
2660      * Moves an item in the queue from one position to another
2661      *
2662      * @param from The position the item is currently at
2663      * @param to The position the item is being moved to
2664      */
2665     public void moveQueueItem(int index1, int index2) {
2666         synchronized (this) {
2667             if (index1 >= mPlaylist.size()) {
2668                 index1 = mPlaylist.size() - 1;
2669             }
2670             if (index2 >= mPlaylist.size()) {
2671                 index2 = mPlaylist.size() - 1;
2672             }
2673
2674             if (index1 == index2) {
2675                 return;
2676             }
2677
2678             final MusicPlaybackTrack track = mPlaylist.remove(index1);
2679             if (index1 < index2) {
2680                 mPlaylist.add(index2, track);
2681                 if (mPlayPos == index1) {
2682                     mPlayPos = index2;
2683                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
2684                     mPlayPos--;
2685                 }
2686             } else if (index2 < index1) {
2687                 mPlaylist.add(index2, track);
2688                 if (mPlayPos == index1) {
2689                     mPlayPos = index2;
2690                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
2691                     mPlayPos++;
2692                 }
2693             }
2694             notifyChange(QUEUE_CHANGED);
2695         }
2696     }
2697
2698     /**
2699      * Sets the repeat mode
2700      *
2701      * @param repeatmode The repeat mode to use
2702      */
2703     public void setRepeatMode(final int repeatmode) {
2704         synchronized (this) {
2705             mRepeatMode = repeatmode;
2706             setNextTrack();
2707             saveQueue(false);
2708             notifyChange(REPEATMODE_CHANGED);
2709         }
2710     }
2711
2712     /**
2713      * Sets the shuffle mode
2714      *
2715      * @param shufflemode The shuffle mode to use
2716      */
2717     public void setShuffleMode(final int shufflemode) {
2718         synchronized (this) {
2719             if (mShuffleMode == shufflemode && mPlaylist.size() > 0) {
2720                 return;
2721             }
2722
2723             mShuffleMode = shufflemode;
2724             if (mShuffleMode == SHUFFLE_AUTO) {
2725                 if (makeAutoShuffleList()) {
2726                     mPlaylist.clear();
2727                     doAutoShuffleUpdate();
2728                     mPlayPos = 0;
2729                     openCurrentAndNext();
2730                     play();
2731                     notifyChange(META_CHANGED);
2732                     return;
2733                 } else {
2734                     mShuffleMode = SHUFFLE_NONE;
2735                 }
2736             } else {
2737                 setNextTrack();
2738             }
2739             saveQueue(false);
2740             notifyChange(SHUFFLEMODE_CHANGED);
2741         }
2742     }
2743
2744     /**
2745      * Sets the position of a track in the queue
2746      *
2747      * @param index The position to place the track
2748      */
2749     public void setQueuePosition(final int index) {
2750         synchronized (this) {
2751             stop(false);
2752             mPlayPos = index;
2753             openCurrentAndNext();
2754             play();
2755             notifyChange(META_CHANGED);
2756             if (mShuffleMode == SHUFFLE_AUTO) {
2757                 doAutoShuffleUpdate();
2758             }
2759         }
2760     }
2761
2762     /**
2763      * Queues a new list for playback
2764      *
2765      * @param list The list to queue
2766      * @param action The action to take
2767      */
2768     public void enqueue(final long[] list, final int action, long sourceId, IdType sourceType) {
2769         synchronized (this) {
2770             if (action == NEXT && mPlayPos + 1 < mPlaylist.size()) {
2771                 addToPlayList(list, mPlayPos + 1, sourceId, sourceType);
2772                 mNextPlayPos = mPlayPos + 1;
2773                 notifyChange(QUEUE_CHANGED);
2774             } else {
2775                 addToPlayList(list, Integer.MAX_VALUE, sourceId, sourceType);
2776                 notifyChange(QUEUE_CHANGED);
2777             }
2778
2779             if (mPlayPos < 0) {
2780                 mPlayPos = 0;
2781                 openCurrentAndNext();
2782                 play();
2783                 notifyChange(META_CHANGED);
2784             }
2785         }
2786     }
2787
2788     /**
2789      * Cycles through the different repeat modes
2790      */
2791     private void cycleRepeat() {
2792         if (mRepeatMode == REPEAT_NONE) {
2793             setRepeatMode(REPEAT_ALL);
2794         } else if (mRepeatMode == REPEAT_ALL) {
2795             setRepeatMode(REPEAT_CURRENT);
2796             if (mShuffleMode != SHUFFLE_NONE) {
2797                 setShuffleMode(SHUFFLE_NONE);
2798             }
2799         } else {
2800             setRepeatMode(REPEAT_NONE);
2801         }
2802     }
2803
2804     /**
2805      * Cycles through the different shuffle modes
2806      */
2807     private void cycleShuffle() {
2808         if (mShuffleMode == SHUFFLE_NONE) {
2809             setShuffleMode(SHUFFLE_NORMAL);
2810             if (mRepeatMode == REPEAT_CURRENT) {
2811                 setRepeatMode(REPEAT_ALL);
2812             }
2813         } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
2814             setShuffleMode(SHUFFLE_NONE);
2815         }
2816     }
2817
2818     /**
2819      * @param smallBitmap true to return a smaller version of the default artwork image.
2820      *                    Currently Has no impact on the artwork size if one exists
2821      * @return The album art for the current album.
2822      */
2823     public BitmapWithColors getAlbumArt(boolean smallBitmap) {
2824         final String albumName = getAlbumName();
2825         final String artistName = getArtistName();
2826         final long albumId = getAlbumId();
2827         final String key = albumName + "_" + artistName + "_" + albumId;
2828         final int targetIndex = smallBitmap ? 0 : 1;
2829
2830         // if the cached key matches and we have the bitmap, return it
2831         if (key.equals(mCachedKey) && mCachedBitmapWithColors[targetIndex] != null) {
2832             return mCachedBitmapWithColors[targetIndex];
2833         }
2834
2835         // otherwise get the artwork (or defaultartwork if none found)
2836         final BitmapWithColors bitmap = mImageFetcher.getArtwork(albumName,
2837                 albumId, artistName, smallBitmap);
2838
2839         // if the key is different, clear the bitmaps first
2840         if (!key.equals(mCachedKey)) {
2841             mCachedBitmapWithColors[0] = null;
2842             mCachedBitmapWithColors[1] = null;
2843         }
2844
2845         // store the new key and bitmap
2846         mCachedKey = key;
2847         mCachedBitmapWithColors[targetIndex] = bitmap;
2848         return bitmap;
2849     }
2850
2851     /**
2852      * Called when one of the lists should refresh or requery.
2853      */
2854     public void refresh() {
2855         notifyChange(REFRESH);
2856     }
2857
2858     /**
2859      * Called when one of the playlists have changed (renamed, added/removed tracks)
2860      */
2861     public void playlistChanged() {
2862         notifyChange(PLAYLIST_CHANGED);
2863     }
2864
2865     /**
2866      * Called to set the status of shake to play feature
2867      */
2868     public void setShakeToPlayEnabled(boolean enabled) {
2869         if (D) {
2870             Log.d(TAG, "ShakeToPlay status: " + enabled);
2871         }
2872         if (enabled) {
2873             if (mShakeDetector == null) {
2874                 mShakeDetector = new ShakeDetector(mShakeDetectorListener);
2875             }
2876             // if song is already playing, start listening immediately
2877             if (isPlaying()) {
2878                 startShakeDetector();
2879             }
2880         }
2881         else {
2882             stopShakeDetector(true);
2883         }
2884     }
2885
2886     /**
2887      * Called to set visibility of album art on lockscreen
2888      */
2889     public void setLockscreenAlbumArt(boolean enabled) {
2890         mShowAlbumArtOnLockscreen = enabled;
2891         notifyChange(META_CHANGED);
2892     }
2893
2894     /**
2895      * Called to start listening to shakes
2896      */
2897     private void startShakeDetector() {
2898         if (mShakeDetector != null) {
2899             mShakeDetector.start((SensorManager)getSystemService(SENSOR_SERVICE));
2900         }
2901     }
2902
2903     /**
2904      * Called to stop listening to shakes
2905      */
2906     private void stopShakeDetector(final boolean destroyShakeDetector) {
2907         if (mShakeDetector != null) {
2908             mShakeDetector.stop();
2909         }
2910         if(destroyShakeDetector){
2911             mShakeDetector = null;
2912             if (D) {
2913                 Log.d(TAG, "ShakeToPlay destroyed!!!");
2914             }
2915         }
2916     }
2917
2918     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2919         /**
2920          * {@inheritDoc}
2921          */
2922         @Override
2923         public void onReceive(final Context context, final Intent intent) {
2924             final String command = intent.getStringExtra(CMDNAME);
2925
2926             if (AppWidgetSmall.CMDAPPWIDGETUPDATE.equals(command)) {
2927                 final int[] small = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2928                 mAppWidgetSmall.performUpdate(MusicPlaybackService.this, small);
2929             } else if (AppWidgetLarge.CMDAPPWIDGETUPDATE.equals(command)) {
2930                 final int[] large = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2931                 mAppWidgetLarge.performUpdate(MusicPlaybackService.this, large);
2932             } else if (AppWidgetLargeAlternate.CMDAPPWIDGETUPDATE.equals(command)) {
2933                 final int[] largeAlt = intent
2934                         .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2935                 mAppWidgetLargeAlternate.performUpdate(MusicPlaybackService.this, largeAlt);
2936             } else {
2937                 handleCommandIntent(intent);
2938             }
2939         }
2940     };
2941
2942     private ContentObserver mMediaStoreObserver;
2943
2944     private class MediaStoreObserver extends ContentObserver implements Runnable {
2945         // milliseconds to delay before calling refresh to aggregate events
2946         private static final long REFRESH_DELAY = 500;
2947         private Handler mHandler;
2948
2949         public MediaStoreObserver(Handler handler) {
2950             super(handler);
2951             mHandler = handler;
2952         }
2953
2954         @Override
2955         public void onChange(boolean selfChange) {
2956             // if a change is detected, remove any scheduled callback
2957             // then post a new one. This is intended to prevent closely
2958             // spaced events from generating multiple refresh calls
2959             mHandler.removeCallbacks(this);
2960             mHandler.postDelayed(this, REFRESH_DELAY);
2961         }
2962
2963         @Override
2964         public void run() {
2965             // actually call refresh when the delayed callback fires
2966             Log.e("ELEVEN", "calling refresh!");
2967             refresh();
2968         }
2969     };
2970
2971     private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
2972         /**
2973          * {@inheritDoc}
2974          */
2975         @Override
2976         public void onAudioFocusChange(final int focusChange) {
2977             mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
2978         }
2979     };
2980
2981     private static final class MusicPlayerHandler extends Handler {
2982         private final WeakReference<MusicPlaybackService> mService;
2983         private float mCurrentVolume = 1.0f;
2984
2985         private static final int DOUBLE_CLICK_TIMEOUT = 800;
2986         private int mHeadsetHookClickCounter = 0;
2987
2988         /**
2989          * Constructor of <code>MusicPlayerHandler</code>
2990          *
2991          * @param service The service to use.
2992          * @param looper The thread to run on.
2993          */
2994         public MusicPlayerHandler(final MusicPlaybackService service, final Looper looper) {
2995             super(looper);
2996             mService = new WeakReference<MusicPlaybackService>(service);
2997         }
2998
2999         /**
3000          * {@inheritDoc}
3001          */
3002         @Override
3003         public void handleMessage(final Message msg) {
3004             final MusicPlaybackService service = mService.get();
3005             if (service == null) {
3006                 return;
3007             }
3008
3009             synchronized (service) {
3010                 switch (msg.what) {
3011                     case FADEDOWN:
3012                         mCurrentVolume -= .05f;
3013                         if (mCurrentVolume > .2f) {
3014                             sendEmptyMessageDelayed(FADEDOWN, 10);
3015                         } else {
3016                             mCurrentVolume = .2f;
3017                         }
3018                         service.mPlayer.setVolume(mCurrentVolume);
3019                         break;
3020                     case FADEUP:
3021                         mCurrentVolume += .01f;
3022                         if (mCurrentVolume < 1.0f) {
3023                             sendEmptyMessageDelayed(FADEUP, 10);
3024                         } else {
3025                             mCurrentVolume = 1.0f;
3026                         }
3027                         service.mPlayer.setVolume(mCurrentVolume);
3028                         break;
3029                     case SERVER_DIED:
3030                         if (service.isPlaying()) {
3031                             final TrackErrorInfo info = (TrackErrorInfo)msg.obj;
3032                             service.sendErrorMessage(info.mTrackName);
3033
3034                             // since the service isPlaying(), we only need to remove the offending
3035                             // audio track, and the code will automatically play the next track
3036                             service.removeTrack(info.mId);
3037                         } else {
3038                             service.openCurrentAndNext();
3039                         }
3040                         break;
3041                     case TRACK_WENT_TO_NEXT:
3042                         service.setAndRecordPlayPos(service.mNextPlayPos);
3043                         service.setNextTrack();
3044                         if (service.mCursor != null) {
3045                             service.mCursor.close();
3046                             service.mCursor = null;
3047                         }
3048                         service.updateCursor(service.mPlaylist.get(service.mPlayPos).mId);
3049                         service.notifyChange(META_CHANGED);
3050                         service.updateNotification();
3051                         break;
3052                     case TRACK_ENDED:
3053                         if (service.mRepeatMode == REPEAT_CURRENT) {
3054                             service.seek(0);
3055                             service.play();
3056                         } else {
3057                             service.gotoNext(false);
3058                         }
3059                         break;
3060                     case LYRICS:
3061                         service.mLyrics = (String) msg.obj;
3062                         service.notifyChange(NEW_LYRICS);
3063                         break;
3064                     case FOCUSCHANGE:
3065                         if (D) Log.d(TAG, "Received audio focus change event " + msg.arg1);
3066                         switch (msg.arg1) {
3067                             case AudioManager.AUDIOFOCUS_LOSS:
3068                             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
3069                                 if (service.isPlaying()) {
3070                                     service.mPausedByTransientLossOfFocus =
3071                                             msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
3072                                 }
3073                                 service.pause();
3074                                 break;
3075                             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
3076                                 removeMessages(FADEUP);
3077                                 sendEmptyMessage(FADEDOWN);
3078                                 break;
3079                             case AudioManager.AUDIOFOCUS_GAIN:
3080                                 if (!service.isPlaying()
3081                                         && service.mPausedByTransientLossOfFocus) {
3082                                     service.mPausedByTransientLossOfFocus = false;
3083                                     mCurrentVolume = 0f;
3084                                     service.mPlayer.setVolume(mCurrentVolume);
3085                                     service.play();
3086                                 } else {
3087                                     removeMessages(FADEDOWN);
3088                                     sendEmptyMessage(FADEUP);
3089                                 }
3090                                 break;
3091                             default:
3092                         }
3093                         break;
3094                     case HEADSET_HOOK_EVENT: {
3095                         long eventTime = (Long) msg.obj;
3096
3097                         mHeadsetHookClickCounter = Math.min(mHeadsetHookClickCounter + 1, 3);
3098                         if (D) Log.d(TAG, "Got headset click, count = " + mHeadsetHookClickCounter);
3099                         removeMessages(HEADSET_HOOK_MULTI_CLICK_TIMEOUT);
3100
3101                         if (mHeadsetHookClickCounter == 3) {
3102                             sendEmptyMessage(HEADSET_HOOK_MULTI_CLICK_TIMEOUT);
3103                         } else {
3104                             sendEmptyMessageAtTime(HEADSET_HOOK_MULTI_CLICK_TIMEOUT,
3105                                     eventTime + DOUBLE_CLICK_TIMEOUT);
3106                         }
3107                         break;
3108                     }
3109                     case HEADSET_HOOK_MULTI_CLICK_TIMEOUT:
3110                         if (D) Log.d(TAG, "Handling headset click");
3111                         switch (mHeadsetHookClickCounter) {
3112                             case 1: service.togglePlayPause(); break;
3113                             case 2: service.gotoNext(true); break;
3114                             case 3: service.prev(false); break;
3115                         }
3116                         mHeadsetHookClickCounter = 0;
3117                         service.mHeadsetHookWakeLock.release();
3118                         break;
3119                     default:
3120                         break;
3121                 }
3122             }
3123         }
3124     }
3125
3126     private static final class Shuffler {
3127
3128         private final LinkedList<Integer> mHistoryOfNumbers = new LinkedList<Integer>();
3129
3130         private final TreeSet<Integer> mPreviousNumbers = new TreeSet<Integer>();
3131
3132         private final Random mRandom = new Random();
3133
3134         private int mPrevious;
3135
3136         /**
3137          * Constructor of <code>Shuffler</code>
3138          */
3139         public Shuffler() {
3140             super();
3141         }
3142
3143         /**
3144          * @param interval The length the queue
3145          * @return The position of the next track to play
3146          */
3147         public int nextInt(final int interval) {
3148             int next;
3149             do {
3150                 next = mRandom.nextInt(interval);
3151             } while (next == mPrevious && interval > 1
3152                     && !mPreviousNumbers.contains(Integer.valueOf(next)));
3153             mPrevious = next;
3154             mHistoryOfNumbers.add(mPrevious);
3155             mPreviousNumbers.add(mPrevious);
3156             cleanUpHistory();
3157             return next;
3158         }
3159
3160         /**
3161          * Removes old tracks and cleans up the history preparing for new tracks
3162          * to be added to the mapping
3163          */
3164         private void cleanUpHistory() {
3165             if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {
3166                 for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {
3167                     mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());
3168                 }
3169             }
3170         }
3171     };
3172
3173     private static final class TrackErrorInfo {
3174         public long mId;
3175         public String mTrackName;
3176
3177         public TrackErrorInfo(long id, String trackName) {
3178             mId = id;
3179             mTrackName = trackName;
3180         }
3181     }
3182
3183     private static final class MultiPlayer implements MediaPlayer.OnErrorListener,
3184             MediaPlayer.OnCompletionListener {
3185
3186         private final WeakReference<MusicPlaybackService> mService;
3187
3188         private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
3189
3190         private MediaPlayer mNextMediaPlayer;
3191
3192         private Handler mHandler;
3193
3194         private boolean mIsInitialized = false;
3195
3196         private SrtManager mSrtManager;
3197
3198         private String mNextMediaPath;
3199
3200         /**
3201          * Constructor of <code>MultiPlayer</code>
3202          */
3203         public MultiPlayer(final MusicPlaybackService service) {
3204             mService = new WeakReference<MusicPlaybackService>(service);
3205             mSrtManager = new SrtManager() {
3206                 @Override
3207                 public void onTimedText(String text) {
3208                     mHandler.obtainMessage(LYRICS, text).sendToTarget();
3209                 }
3210             };
3211         }
3212
3213         /**
3214          * @param path The path of the file, or the http/rtsp URL of the stream
3215          *            you want to play
3216          */
3217         public void setDataSource(final String path) {
3218             mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
3219             if (mIsInitialized) {
3220                 loadSrt(path);
3221                 setNextDataSource(null);
3222             }
3223         }
3224
3225         private void loadSrt(final String path) {
3226             mSrtManager.reset();
3227
3228             Uri uri = Uri.parse(path);
3229             String filePath = null;
3230
3231             if (path.startsWith("content://")) {
3232                 // resolve the content resolver path to a file path
3233                 Cursor cursor = null;
3234                 try {
3235                     final String[] proj = {MediaStore.Audio.Media.DATA};
3236                     cursor = mService.get().getContentResolver().query(uri, proj,
3237                             null, null, null);
3238                     if (cursor != null && cursor.moveToFirst()) {
3239                         filePath = cursor.getString(0);
3240                     }
3241                 } finally {
3242                     if (cursor != null) {
3243                         cursor.close();
3244                         cursor = null;
3245                     }
3246                 }
3247             } else {
3248                 filePath = uri.getPath();
3249             }
3250
3251             if (!TextUtils.isEmpty(filePath)) {
3252                 final int lastIndex = filePath.lastIndexOf('.');
3253                 if (lastIndex != -1) {
3254                     String newPath = filePath.substring(0, lastIndex) + ".srt";
3255                     final File f = new File(newPath);
3256
3257                     mSrtManager.initialize(mCurrentMediaPlayer, f);
3258                 }
3259             }
3260         }
3261
3262         /**
3263          * @param player The {@link MediaPlayer} to use
3264          * @param path The path of the file, or the http/rtsp URL of the stream
3265          *            you want to play
3266          * @return True if the <code>player</code> has been prepared and is
3267          *         ready to play, false otherwise
3268          */
3269         private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
3270             try {
3271                 player.reset();
3272                 player.setOnPreparedListener(null);
3273                 if (path.startsWith("content://")) {
3274                     player.setDataSource(mService.get(), Uri.parse(path));
3275                 } else {
3276                     player.setDataSource(path);
3277                 }
3278                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
3279
3280                 player.prepare();
3281             } catch (final IOException todo) {
3282                 // TODO: notify the user why the file couldn't be opened
3283                 return false;
3284             } catch (final IllegalArgumentException todo) {
3285                 // TODO: notify the user why the file couldn't be opened
3286                 return false;
3287             }
3288             player.setOnCompletionListener(this);
3289             player.setOnErrorListener(this);
3290             return true;
3291         }
3292
3293         /**
3294          * Set the MediaPlayer to start when this MediaPlayer finishes playback.
3295          *
3296          * @param path The path of the file, or the http/rtsp URL of the stream
3297          *            you want to play
3298          */
3299         public void setNextDataSource(final String path) {
3300             mNextMediaPath = null;
3301             try {
3302                 mCurrentMediaPlayer.setNextMediaPlayer(null);
3303             } catch (IllegalArgumentException e) {
3304                 Log.i(TAG, "Next media player is current one, continuing");
3305             } catch (IllegalStateException e) {
3306                 Log.e(TAG, "Media player not initialized!");
3307                 return;
3308             }
3309             if (mNextMediaPlayer != null) {
3310                 mNextMediaPlayer.release();
3311                 mNextMediaPlayer = null;
3312             }
3313             if (path == null) {
3314                 return;
3315             }
3316             mNextMediaPlayer = new MediaPlayer();
3317             mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
3318             if (setDataSourceImpl(mNextMediaPlayer, path)) {
3319                 mNextMediaPath = path;
3320                 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
3321             } else {
3322                 if (mNextMediaPlayer != null) {
3323                     mNextMediaPlayer.release();
3324                     mNextMediaPlayer = null;
3325                 }
3326             }
3327         }
3328
3329         /**
3330          * Sets the handler
3331          *
3332          * @param handler The handler to use
3333          */
3334         public void setHandler(final Handler handler) {
3335             mHandler = handler;
3336         }
3337
3338         /**
3339          * @return True if the player is ready to go, false otherwise
3340          */
3341         public boolean isInitialized() {
3342             return mIsInitialized;
3343         }
3344
3345         /**
3346          * Starts or resumes playback.
3347          */
3348         public void start() {
3349             mCurrentMediaPlayer.start();
3350             mSrtManager.play();
3351         }
3352
3353         /**
3354          * Resets the MediaPlayer to its uninitialized state.
3355          */
3356         public void stop() {
3357             mCurrentMediaPlayer.reset();
3358             mSrtManager.reset();
3359             mIsInitialized = false;
3360         }
3361
3362         /**
3363          * Releases resources associated with this MediaPlayer object.
3364          */
3365         public void release() {
3366             mCurrentMediaPlayer.release();
3367             mSrtManager.release();
3368             mSrtManager = null;
3369         }
3370
3371         /**
3372          * Pauses playback. Call start() to resume.
3373          */
3374         public void pause() {
3375             mCurrentMediaPlayer.pause();
3376             mSrtManager.pause();
3377         }
3378
3379         /**
3380          * Gets the duration of the file.
3381          *
3382          * @return The duration in milliseconds
3383          */
3384         public long duration() {
3385             return mCurrentMediaPlayer.getDuration();
3386         }
3387
3388         /**
3389          * Gets the current playback position.
3390          *
3391          * @return The current position in milliseconds
3392          */
3393         public long position() {
3394             return mCurrentMediaPlayer.getCurrentPosition();
3395         }
3396
3397         /**
3398          * Gets the current playback position.
3399          *
3400          * @param whereto The offset in milliseconds from the start to seek to
3401          * @return The offset in milliseconds from the start to seek to
3402          */
3403         public long seek(final long whereto) {
3404             mCurrentMediaPlayer.seekTo((int)whereto);
3405             mSrtManager.seekTo(whereto);
3406             return whereto;
3407         }
3408
3409         /**
3410          * Sets the volume on this player.
3411          *
3412          * @param vol Left and right volume scalar
3413          */
3414         public void setVolume(final float vol) {
3415             mCurrentMediaPlayer.setVolume(vol, vol);
3416         }
3417
3418         /**
3419          * Sets the audio session ID.
3420          *
3421          * @param sessionId The audio session ID
3422          */
3423         public void setAudioSessionId(final int sessionId) {
3424             mCurrentMediaPlayer.setAudioSessionId(sessionId);
3425         }
3426
3427         /**
3428          * Returns the audio session ID.
3429          *
3430          * @return The current audio session ID.
3431          */
3432         public int getAudioSessionId() {
3433             return mCurrentMediaPlayer.getAudioSessionId();
3434         }
3435
3436         /**
3437          * {@inheritDoc}
3438          */
3439         @Override
3440         public boolean onError(final MediaPlayer mp, final int what, final int extra) {
3441             Log.w(TAG, "Music Server Error what: " + what + " extra: " + extra);
3442             switch (what) {
3443                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
3444                     final MusicPlaybackService service = mService.get();
3445                     if (service == null) {
3446                         return false;
3447                     }
3448                     final TrackErrorInfo errorInfo = new TrackErrorInfo(service.getAudioId(),
3449                             service.getTrackName());
3450
3451                     mIsInitialized = false;
3452                     mCurrentMediaPlayer.release();
3453                     mCurrentMediaPlayer = new MediaPlayer();
3454                     Message msg = mHandler.obtainMessage(SERVER_DIED, errorInfo);
3455                     mHandler.sendMessageDelayed(msg, 2000);
3456                     return true;
3457                 default:
3458                     break;
3459             }
3460             return false;
3461         }
3462
3463         /**
3464          * {@inheritDoc}
3465          */
3466         @Override
3467         public void onCompletion(final MediaPlayer mp) {
3468             if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
3469                 mCurrentMediaPlayer.release();
3470                 mCurrentMediaPlayer = mNextMediaPlayer;
3471                 loadSrt(mNextMediaPath);
3472                 mNextMediaPath = null;
3473                 mNextMediaPlayer = null;
3474                 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
3475             } else {
3476                 mHandler.sendEmptyMessage(TRACK_ENDED);
3477             }
3478         }
3479     }
3480
3481     private static final class ServiceStub extends IElevenService.Stub {
3482
3483         private final WeakReference<MusicPlaybackService> mService;
3484
3485         private ServiceStub(final MusicPlaybackService service) {
3486             mService = new WeakReference<MusicPlaybackService>(service);
3487         }
3488
3489         /**
3490          * {@inheritDoc}
3491          */
3492         @Override
3493         public void openFile(final String path) throws RemoteException {
3494             mService.get().openFile(path);
3495         }
3496
3497         /**
3498          * {@inheritDoc}
3499          */
3500         @Override
3501         public void open(final long[] list, final int position, long sourceId, int sourceType)
3502                 throws RemoteException {
3503             mService.get().open(list, position, sourceId, IdType.getTypeById(sourceType));
3504         }
3505
3506         /**
3507          * {@inheritDoc}
3508          */
3509         @Override
3510         public void stop() throws RemoteException {
3511             mService.get().stop();
3512         }
3513
3514         /**
3515          * {@inheritDoc}
3516          */
3517         @Override
3518         public void pause() throws RemoteException {
3519             mService.get().pause();
3520         }
3521
3522         /**
3523          * {@inheritDoc}
3524          */
3525         @Override
3526         public void play() throws RemoteException {
3527             mService.get().play();
3528         }
3529
3530         /**
3531          * {@inheritDoc}
3532          */
3533         @Override
3534         public void prev(boolean forcePrevious) throws RemoteException {
3535             mService.get().prev(forcePrevious);
3536         }
3537
3538         /**
3539          * {@inheritDoc}
3540          */
3541         @Override
3542         public void next() throws RemoteException {
3543             mService.get().gotoNext(true);
3544         }
3545
3546         /**
3547          * {@inheritDoc}
3548          */
3549         @Override
3550         public void enqueue(final long[] list, final int action, long sourceId, int sourceType)
3551                 throws RemoteException {
3552             mService.get().enqueue(list, action, sourceId, IdType.getTypeById(sourceType));
3553         }
3554
3555         /**
3556          * {@inheritDoc}
3557          */
3558         @Override
3559         public void setQueuePosition(final int index) throws RemoteException {
3560             mService.get().setQueuePosition(index);
3561         }
3562
3563         /**
3564          * {@inheritDoc}
3565          */
3566         @Override
3567         public void setShuffleMode(final int shufflemode) throws RemoteException {
3568             mService.get().setShuffleMode(shufflemode);
3569         }
3570
3571         /**
3572          * {@inheritDoc}
3573          */
3574         @Override
3575         public void setRepeatMode(final int repeatmode) throws RemoteException {
3576             mService.get().setRepeatMode(repeatmode);
3577         }
3578
3579         /**
3580          * {@inheritDoc}
3581          */
3582         @Override
3583         public void moveQueueItem(final int from, final int to) throws RemoteException {
3584             mService.get().moveQueueItem(from, to);
3585         }
3586
3587         /**
3588          * {@inheritDoc}
3589          */
3590         @Override
3591         public void refresh() throws RemoteException {
3592             mService.get().refresh();
3593         }
3594
3595         /**
3596          * {@inheritDoc}
3597          */
3598         @Override
3599         public void playlistChanged() throws RemoteException {
3600             mService.get().playlistChanged();
3601         }
3602
3603         /**
3604          * {@inheritDoc}
3605          */
3606         @Override
3607         public boolean isPlaying() throws RemoteException {
3608             return mService.get().isPlaying();
3609         }
3610
3611         /**
3612          * {@inheritDoc}
3613          */
3614         @Override
3615         public long[] getQueue() throws RemoteException {
3616             return mService.get().getQueue();
3617         }
3618
3619         /**
3620          * {@inheritDoc}
3621          */
3622         @Override
3623         public long getQueueItemAtPosition(int position) throws RemoteException {
3624             return mService.get().getQueueItemAtPosition(position);
3625         }
3626
3627         /**
3628          * {@inheritDoc}
3629          */
3630         @Override
3631         public int getQueueSize() throws RemoteException {
3632             return mService.get().getQueueSize();
3633         }
3634
3635         /**
3636          * {@inheritDoc}
3637          */
3638         @Override
3639         public int getQueueHistoryPosition(int position) throws RemoteException {
3640             return mService.get().getQueueHistoryPosition(position);
3641         }
3642
3643         /**
3644          * {@inheritDoc}
3645          */
3646         @Override
3647         public int getQueueHistorySize() throws RemoteException {
3648             return mService.get().getQueueHistorySize();
3649         }
3650
3651         /**
3652          * {@inheritDoc}
3653          */
3654         @Override
3655         public int[] getQueueHistoryList() throws RemoteException {
3656             return mService.get().getQueueHistoryList();
3657         }
3658
3659         /**
3660          * {@inheritDoc}
3661          */
3662         @Override
3663         public long duration() throws RemoteException {
3664             return mService.get().duration();
3665         }
3666
3667         /**
3668          * {@inheritDoc}
3669          */
3670         @Override
3671         public long position() throws RemoteException {
3672             return mService.get().position();
3673         }
3674
3675         /**
3676          * {@inheritDoc}
3677          */
3678         @Override
3679         public long seek(final long position) throws RemoteException {
3680             return mService.get().seek(position);
3681         }
3682
3683         /**
3684          * {@inheritDoc}
3685          */
3686         @Override
3687         public void seekRelative(final long deltaInMs) throws RemoteException {
3688             mService.get().seekRelative(deltaInMs);
3689         }
3690
3691         /**
3692          * {@inheritDoc}
3693          */
3694         @Override
3695         public long getAudioId() throws RemoteException {
3696             return mService.get().getAudioId();
3697         }
3698
3699         /**
3700          * {@inheritDoc}
3701          */
3702         @Override
3703         public MusicPlaybackTrack getCurrentTrack() throws RemoteException {
3704             return mService.get().getCurrentTrack();
3705         }
3706
3707         /**
3708          * {@inheritDoc}
3709          */
3710         @Override
3711         public MusicPlaybackTrack getTrack(int index) throws RemoteException {
3712             return mService.get().getTrack(index);
3713         }
3714
3715                 /**
3716          * {@inheritDoc}
3717          */
3718         @Override
3719         public long getNextAudioId() throws RemoteException {
3720             return mService.get().getNextAudioId();
3721         }
3722
3723         /**
3724          * {@inheritDoc}
3725          */
3726         @Override
3727         public long getPreviousAudioId() throws RemoteException {
3728             return mService.get().getPreviousAudioId();
3729         }
3730
3731         /**
3732          * {@inheritDoc}
3733          */
3734         @Override
3735         public long getArtistId() throws RemoteException {
3736             return mService.get().getArtistId();
3737         }
3738
3739         /**
3740          * {@inheritDoc}
3741          */
3742         @Override
3743         public long getAlbumId() throws RemoteException {
3744             return mService.get().getAlbumId();
3745         }
3746
3747         /**
3748          * {@inheritDoc}
3749          */
3750         @Override
3751         public String getArtistName() throws RemoteException {
3752             return mService.get().getArtistName();
3753         }
3754
3755         /**
3756          * {@inheritDoc}
3757          */
3758         @Override
3759         public String getTrackName() throws RemoteException {
3760             return mService.get().getTrackName();
3761         }
3762
3763         /**
3764          * {@inheritDoc}
3765          */
3766         @Override
3767         public String getAlbumName() throws RemoteException {
3768             return mService.get().getAlbumName();
3769         }
3770
3771         /**
3772          * {@inheritDoc}
3773          */
3774         @Override
3775         public String getPath() throws RemoteException {
3776             return mService.get().getPath();
3777         }
3778
3779         /**
3780          * {@inheritDoc}
3781          */
3782         @Override
3783         public int getQueuePosition() throws RemoteException {
3784             return mService.get().getQueuePosition();
3785         }
3786
3787         /**
3788          * {@inheritDoc}
3789          */
3790         @Override
3791         public int getShuffleMode() throws RemoteException {
3792             return mService.get().getShuffleMode();
3793         }
3794
3795         /**
3796          * {@inheritDoc}
3797          */
3798         @Override
3799         public int getRepeatMode() throws RemoteException {
3800             return mService.get().getRepeatMode();
3801         }
3802
3803         /**
3804          * {@inheritDoc}
3805          */
3806         @Override
3807         public int removeTracks(final int first, final int last) throws RemoteException {
3808             return mService.get().removeTracks(first, last);
3809         }
3810
3811         /**
3812          * {@inheritDoc}
3813          */
3814         @Override
3815         public int removeTrack(final long id) throws RemoteException {
3816             return mService.get().removeTrack(id);
3817         }
3818
3819         /**
3820          * {@inheritDoc}
3821          */
3822         @Override
3823         public boolean removeTrackAtPosition(final long id, final int position)
3824                 throws RemoteException {
3825             return mService.get().removeTrackAtPosition(id, position);
3826         }
3827
3828         /**
3829          * {@inheritDoc}
3830          */
3831         @Override
3832         public int getMediaMountedCount() throws RemoteException {
3833             return mService.get().getMediaMountedCount();
3834         }
3835
3836         /**
3837          * {@inheritDoc}
3838          */
3839         @Override
3840         public int getAudioSessionId() throws RemoteException {
3841             return mService.get().getAudioSessionId();
3842         }
3843
3844         /**
3845          * {@inheritDoc}
3846          */
3847         @Override
3848         public void setShakeToPlayEnabled(boolean enabled) {
3849             mService.get().setShakeToPlayEnabled(enabled);
3850         }
3851
3852         /**
3853          * {@inheritDoc}
3854          */
3855         @Override
3856         public void setLockscreenAlbumArt(boolean enabled) {
3857             mService.get().setLockscreenAlbumArt(enabled);
3858         }
3859
3860     }
3861
3862     private class QueueUpdateTask extends AsyncTask<Void, Void, List<MediaSession.QueueItem>> {
3863         private long[] mQueue;
3864
3865         public QueueUpdateTask(long[] queue) {
3866             mQueue = queue != null ? Arrays.copyOf(queue, queue.length) : null;
3867         }
3868
3869         @Override
3870         protected List<MediaSession.QueueItem> doInBackground(Void... params) {
3871             if (mQueue == null || mQueue.length == 0) {
3872                 return null;
3873             }
3874
3875             final StringBuilder selection = new StringBuilder();
3876             selection.append(MediaStore.Audio.Media._ID).append(" IN (");
3877             for (int i = 0; i < mQueue.length; i++) {
3878                 if (i != 0) {
3879                     selection.append(",");
3880                 }
3881                 selection.append(mQueue[i]);
3882             }
3883             selection.append(")");
3884
3885             Cursor c = getContentResolver().query(
3886                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
3887                     new String[] { AudioColumns._ID, AudioColumns.TITLE, AudioColumns.ARTIST },
3888                     selection.toString(), null, null);
3889             if (c == null) {
3890                 return null;
3891             }
3892
3893             try {
3894                 LongSparseArray<MediaDescription> descsById = new LongSparseArray<>();
3895                 final int idColumnIndex = c.getColumnIndexOrThrow(AudioColumns._ID);
3896                 final int titleColumnIndex = c.getColumnIndexOrThrow(AudioColumns.TITLE);
3897                 final int artistColumnIndex = c.getColumnIndexOrThrow(AudioColumns.ARTIST);
3898
3899                 while (c.moveToNext() && !isCancelled()) {
3900                     final MediaDescription desc = new MediaDescription.Builder()
3901                             .setTitle(c.getString(titleColumnIndex))
3902                             .setSubtitle(c.getString(artistColumnIndex))
3903                             .build();
3904                     final long id = c.getLong(idColumnIndex);
3905                     descsById.put(id, desc);
3906                 }
3907
3908                 List<MediaSession.QueueItem> items = new ArrayList<>();
3909                 for (int i = 0; i < mQueue.length; i++) {
3910                     MediaDescription desc = descsById.get(mQueue[i]);
3911                     if (desc == null) {
3912                         // shouldn't happen except in corner cases like
3913                         // music being deleted while we were processing
3914                         desc = new MediaDescription.Builder().build();
3915                     }
3916                     items.add(new MediaSession.QueueItem(desc, i));
3917                 }
3918                 return items;
3919             } finally {
3920                 c.close();
3921             }
3922         }
3923
3924         @Override
3925         protected void onPostExecute(List<MediaSession.QueueItem> items) {
3926             if (!isCancelled()) {
3927                 mSession.setQueue(items);
3928             }
3929         }
3930     }
3931 }