1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.music;
18 
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.app.Service;
22 import android.appwidget.AppWidgetManager;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.BroadcastReceiver;
31 import android.content.SharedPreferences;
32 import android.content.SharedPreferences.Editor;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteException;
35 import android.graphics.Bitmap;
36 import android.media.audiofx.AudioEffect;
37 import android.media.AudioManager;
38 import android.media.AudioManager.OnAudioFocusChangeListener;
39 import android.media.MediaMetadataRetriever;
40 import android.media.MediaPlayer;
41 import android.media.MediaPlayer.OnCompletionListener;
42 import android.media.RemoteControlClient;
43 import android.media.RemoteControlClient.MetadataEditor;
44 import android.net.Uri;
45 import android.os.Handler;
46 import android.os.IBinder;
47 import android.os.Message;
48 import android.os.PowerManager;
49 import android.os.SystemClock;
50 import android.os.PowerManager.WakeLock;
51 import android.provider.MediaStore;
52 import android.util.Log;
53 import android.widget.RemoteViews;
54 import android.widget.Toast;
55 
56 import java.io.FileDescriptor;
57 import java.io.IOException;
58 import java.io.PrintWriter;
59 import java.lang.ref.WeakReference;
60 import java.util.Random;
61 import java.util.Vector;
62 
63 /**
64  * Provides "background" audio playback capabilities, allowing the
65  * user to switch between activities without stopping playback.
66  */
67 public class MediaPlaybackService extends Service {
68     /** used to specify whether enqueue() should start playing
69      * the new list of files right away, next or once all the currently
70      * queued files have been played
71      */
72     public static final int NOW = 1;
73     public static final int NEXT = 2;
74     public static final int LAST = 3;
75     public static final int PLAYBACKSERVICE_STATUS = 1;
76 
77     public static final int SHUFFLE_NONE = 0;
78     public static final int SHUFFLE_NORMAL = 1;
79     public static final int SHUFFLE_AUTO = 2;
80 
81     public static final int REPEAT_NONE = 0;
82     public static final int REPEAT_CURRENT = 1;
83     public static final int REPEAT_ALL = 2;
84 
85     public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
86     public static final String META_CHANGED = "com.android.music.metachanged";
87     public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
88 
89     public static final String SERVICECMD = "com.android.music.musicservicecommand";
90     public static final String CMDNAME = "command";
91     public static final String CMDTOGGLEPAUSE = "togglepause";
92     public static final String CMDSTOP = "stop";
93     public static final String CMDPAUSE = "pause";
94     public static final String CMDPLAY = "play";
95     public static final String CMDPREVIOUS = "previous";
96     public static final String CMDNEXT = "next";
97 
98     public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
99     public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
100     public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
101     public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
102 
103     private static final int TRACK_ENDED = 1;
104     private static final int RELEASE_WAKELOCK = 2;
105     private static final int SERVER_DIED = 3;
106     private static final int FOCUSCHANGE = 4;
107     private static final int FADEDOWN = 5;
108     private static final int FADEUP = 6;
109     private static final int TRACK_WENT_TO_NEXT = 7;
110     private static final int MAX_HISTORY_SIZE = 100;
111 
112     private MultiPlayer mPlayer;
113     private String mFileToPlay;
114     private int mShuffleMode = SHUFFLE_NONE;
115     private int mRepeatMode = REPEAT_NONE;
116     private int mMediaMountedCount = 0;
117     private long [] mAutoShuffleList = null;
118     private long [] mPlayList = null;
119     private int mPlayListLen = 0;
120     private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
121     private Cursor mCursor;
122     private int mPlayPos = -1;
123     private int mNextPlayPos = -1;
124     private static final String LOGTAG = "MediaPlaybackService";
125     private final Shuffler mRand = new Shuffler();
126     private int mOpenFailedCounter = 0;
127     String[] mCursorCols = new String[] {
128             "audio._id AS _id",             // index must match IDCOLIDX below
129             MediaStore.Audio.Media.ARTIST,
130             MediaStore.Audio.Media.ALBUM,
131             MediaStore.Audio.Media.TITLE,
132             MediaStore.Audio.Media.DATA,
133             MediaStore.Audio.Media.MIME_TYPE,
134             MediaStore.Audio.Media.ALBUM_ID,
135             MediaStore.Audio.Media.ARTIST_ID,
136             MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
137             MediaStore.Audio.Media.BOOKMARK    // index must match BOOKMARKCOLIDX below
138     };
139     private final static int IDCOLIDX = 0;
140     private final static int PODCASTCOLIDX = 8;
141     private final static int BOOKMARKCOLIDX = 9;
142     private BroadcastReceiver mUnmountReceiver = null;
143     private WakeLock mWakeLock;
144     private int mServiceStartId = -1;
145     private boolean mServiceInUse = false;
146     private boolean mIsSupposedToBePlaying = false;
147     private boolean mQuietMode = false;
148     private AudioManager mAudioManager;
149     private boolean mQueueIsSaveable = true;
150     // used to track what type of audio focus loss caused the playback to pause
151     private boolean mPausedByTransientLossOfFocus = false;
152 
153     private SharedPreferences mPreferences;
154     // We use this to distinguish between different cards when saving/restoring playlists.
155     // This will have to change if we want to support multiple simultaneous cards.
156     private int mCardId;
157 
158     private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
159 
160     // interval after which we stop the service when idle
161     private static final int IDLE_DELAY = 60000;
162 
163     private RemoteControlClient mRemoteControlClient;
164 
165     private Handler mMediaplayerHandler = new Handler() {
166         float mCurrentVolume = 1.0f;
167         @Override
168         public void handleMessage(Message msg) {
169             MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
170             switch (msg.what) {
171                 case FADEDOWN:
172                     mCurrentVolume -= .05f;
173                     if (mCurrentVolume > .2f) {
174                         mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
175                     } else {
176                         mCurrentVolume = .2f;
177                     }
178                     mPlayer.setVolume(mCurrentVolume);
179                     break;
180                 case FADEUP:
181                     mCurrentVolume += .01f;
182                     if (mCurrentVolume < 1.0f) {
183                         mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
184                     } else {
185                         mCurrentVolume = 1.0f;
186                     }
187                     mPlayer.setVolume(mCurrentVolume);
188                     break;
189                 case SERVER_DIED:
190                     if (mIsSupposedToBePlaying) {
191                         gotoNext(true);
192                     } else {
193                         // the server died when we were idle, so just
194                         // reopen the same song (it will start again
195                         // from the beginning though when the user
196                         // restarts)
197                         openCurrentAndNext();
198                     }
199                     break;
200                 case TRACK_WENT_TO_NEXT:
201                     mPlayPos = mNextPlayPos;
202                     if (mCursor != null) {
203                         mCursor.close();
204                         mCursor = null;
205                     }
206                     if (mPlayPos >= 0 && mPlayPos < mPlayList.length) {
207                         mCursor = getCursorForId(mPlayList[mPlayPos]);
208                     }
209                     notifyChange(META_CHANGED);
210                     updateNotification();
211                     setNextTrack();
212                     break;
213                 case TRACK_ENDED:
214                     if (mRepeatMode == REPEAT_CURRENT) {
215                         seek(0);
216                         play();
217                     } else {
218                         gotoNext(false);
219                     }
220                     break;
221                 case RELEASE_WAKELOCK:
222                     mWakeLock.release();
223                     break;
224 
225                 case FOCUSCHANGE:
226                     // This code is here so we can better synchronize it with the code that
227                     // handles fade-in
228                     switch (msg.arg1) {
229                         case AudioManager.AUDIOFOCUS_LOSS:
230                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
231                             if(isPlaying()) {
232                                 mPausedByTransientLossOfFocus = false;
233                             }
234                             pause();
235                             break;
236                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
237                             mMediaplayerHandler.removeMessages(FADEUP);
238                             mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
239                             break;
240                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
241                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
242                             if(isPlaying()) {
243                                 mPausedByTransientLossOfFocus = true;
244                             }
245                             pause();
246                             break;
247                         case AudioManager.AUDIOFOCUS_GAIN:
248                             Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
249                             if(!isPlaying() && mPausedByTransientLossOfFocus) {
250                                 mPausedByTransientLossOfFocus = false;
251                                 mCurrentVolume = 0f;
252                                 mPlayer.setVolume(mCurrentVolume);
253                                 play(); // also queues a fade-in
254                             } else {
255                                 mMediaplayerHandler.removeMessages(FADEDOWN);
256                                 mMediaplayerHandler.sendEmptyMessage(FADEUP);
257                             }
258                             break;
259                         default:
260                             Log.e(LOGTAG, "Unknown audio focus change code");
261                     }
262                     break;
263 
264                 default:
265                     break;
266             }
267         }
268     };
269 
270     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
271         @Override
272         public void onReceive(Context context, Intent intent) {
273             String action = intent.getAction();
274             String cmd = intent.getStringExtra("command");
275             MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
276             if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
277                 gotoNext(true);
278             } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
279                 prev();
280             } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
281                 if (isPlaying()) {
282                     pause();
283                     mPausedByTransientLossOfFocus = false;
284                 } else {
285                     play();
286                 }
287             } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
288                 pause();
289                 mPausedByTransientLossOfFocus = false;
290             } else if (CMDPLAY.equals(cmd)) {
291                 play();
292             } else if (CMDSTOP.equals(cmd)) {
293                 pause();
294                 mPausedByTransientLossOfFocus = false;
295                 seek(0);
296             } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
297                 // Someone asked us to refresh a set of specific widgets, probably
298                 // because they were just added.
299                 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
300                 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
301             }
302         }
303     };
304 
305     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
306         public void onAudioFocusChange(int focusChange) {
307             mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
308         }
309     };
310 
MediaPlaybackService()311     public MediaPlaybackService() {
312     }
313 
314     @Override
onCreate()315     public void onCreate() {
316         super.onCreate();
317 
318         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
319         ComponentName rec = new ComponentName(getPackageName(),
320                 MediaButtonIntentReceiver.class.getName());
321         mAudioManager.registerMediaButtonEventReceiver(rec);
322 
323         Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
324         i.setComponent(rec);
325         PendingIntent pi = PendingIntent.getBroadcast(this /*context*/,
326                 0 /*requestCode, ignored*/, i /*intent*/, 0 /*flags*/);
327         mRemoteControlClient = new RemoteControlClient(pi);
328         mAudioManager.registerRemoteControlClient(mRemoteControlClient);
329 
330         int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
331                 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
332                 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
333                 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
334                 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
335                 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
336         mRemoteControlClient.setTransportControlFlags(flags);
337 
338         mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
339         mCardId = MusicUtils.getCardId(this);
340 
341         registerExternalStorageListener();
342 
343         // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
344         mPlayer = new MultiPlayer();
345         mPlayer.setHandler(mMediaplayerHandler);
346 
347         reloadQueue();
348         notifyChange(QUEUE_CHANGED);
349         notifyChange(META_CHANGED);
350 
351         IntentFilter commandFilter = new IntentFilter();
352         commandFilter.addAction(SERVICECMD);
353         commandFilter.addAction(TOGGLEPAUSE_ACTION);
354         commandFilter.addAction(PAUSE_ACTION);
355         commandFilter.addAction(NEXT_ACTION);
356         commandFilter.addAction(PREVIOUS_ACTION);
357         registerReceiver(mIntentReceiver, commandFilter);
358 
359         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
360         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
361         mWakeLock.setReferenceCounted(false);
362 
363         // If the service was idle, but got killed before it stopped itself, the
364         // system will relaunch it. Make sure it gets stopped again in that case.
365         Message msg = mDelayedStopHandler.obtainMessage();
366         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
367     }
368 
369     @Override
onDestroy()370     public void onDestroy() {
371         // Check that we're not being destroyed while something is still playing.
372         if (isPlaying()) {
373             Log.e(LOGTAG, "Service being destroyed while still playing.");
374         }
375         // release all MediaPlayer resources, including the native player and wakelocks
376         Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
377         i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
378         i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
379         sendBroadcast(i);
380         mPlayer.release();
381         mPlayer = null;
382 
383         mAudioManager.abandonAudioFocus(mAudioFocusListener);
384         mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
385 
386         // make sure there aren't any other messages coming
387         mDelayedStopHandler.removeCallbacksAndMessages(null);
388         mMediaplayerHandler.removeCallbacksAndMessages(null);
389 
390         if (mCursor != null) {
391             mCursor.close();
392             mCursor = null;
393         }
394 
395         unregisterReceiver(mIntentReceiver);
396         if (mUnmountReceiver != null) {
397             unregisterReceiver(mUnmountReceiver);
398             mUnmountReceiver = null;
399         }
400         mWakeLock.release();
401         super.onDestroy();
402     }
403 
404     private final char hexdigits [] = new char [] {
405             '0', '1', '2', '3',
406             '4', '5', '6', '7',
407             '8', '9', 'a', 'b',
408             'c', 'd', 'e', 'f'
409     };
410 
saveQueue(boolean full)411     private void saveQueue(boolean full) {
412         if (!mQueueIsSaveable) {
413             return;
414         }
415 
416         Editor ed = mPreferences.edit();
417         //long start = System.currentTimeMillis();
418         if (full) {
419             StringBuilder q = new StringBuilder();
420 
421             // The current playlist is saved as a list of "reverse hexadecimal"
422             // numbers, which we can generate faster than normal decimal or
423             // hexadecimal numbers, which in turn allows us to save the playlist
424             // more often without worrying too much about performance.
425             // (saving the full state takes about 40 ms under no-load conditions
426             // on the phone)
427             int len = mPlayListLen;
428             for (int i = 0; i < len; i++) {
429                 long n = mPlayList[i];
430                 if (n < 0) {
431                     continue;
432                 } else if (n == 0) {
433                     q.append("0;");
434                 } else {
435                     while (n != 0) {
436                         int digit = (int)(n & 0xf);
437                         n >>>= 4;
438                         q.append(hexdigits[digit]);
439                     }
440                     q.append(";");
441                 }
442             }
443             //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
444             ed.putString("queue", q.toString());
445             ed.putInt("cardid", mCardId);
446             if (mShuffleMode != SHUFFLE_NONE) {
447                 // In shuffle mode we need to save the history too
448                 len = mHistory.size();
449                 q.setLength(0);
450                 for (int i = 0; i < len; i++) {
451                     int n = mHistory.get(i);
452                     if (n == 0) {
453                         q.append("0;");
454                     } else {
455                         while (n != 0) {
456                             int digit = (n & 0xf);
457                             n >>>= 4;
458                             q.append(hexdigits[digit]);
459                         }
460                         q.append(";");
461                     }
462                 }
463                 ed.putString("history", q.toString());
464             }
465         }
466         ed.putInt("curpos", mPlayPos);
467         if (mPlayer.isInitialized()) {
468             ed.putLong("seekpos", mPlayer.position());
469         }
470         ed.putInt("repeatmode", mRepeatMode);
471         ed.putInt("shufflemode", mShuffleMode);
472         SharedPreferencesCompat.apply(ed);
473 
474         //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
475     }
476 
reloadQueue()477     private void reloadQueue() {
478         String q = null;
479 
480         boolean newstyle = false;
481         int id = mCardId;
482         if (mPreferences.contains("cardid")) {
483             newstyle = true;
484             id = mPreferences.getInt("cardid", ~mCardId);
485         }
486         if (id == mCardId) {
487             // Only restore the saved playlist if the card is still
488             // the same one as when the playlist was saved
489             q = mPreferences.getString("queue", "");
490         }
491         int qlen = q != null ? q.length() : 0;
492         if (qlen > 1) {
493             //Log.i("@@@@ service", "loaded queue: " + q);
494             int plen = 0;
495             int n = 0;
496             int shift = 0;
497             for (int i = 0; i < qlen; i++) {
498                 char c = q.charAt(i);
499                 if (c == ';') {
500                     ensurePlayListCapacity(plen + 1);
501                     mPlayList[plen] = n;
502                     plen++;
503                     n = 0;
504                     shift = 0;
505                 } else {
506                     if (c >= '0' && c <= '9') {
507                         n += ((c - '0') << shift);
508                     } else if (c >= 'a' && c <= 'f') {
509                         n += ((10 + c - 'a') << shift);
510                     } else {
511                         // bogus playlist data
512                         plen = 0;
513                         break;
514                     }
515                     shift += 4;
516                 }
517             }
518             mPlayListLen = plen;
519 
520             int pos = mPreferences.getInt("curpos", 0);
521             if (pos < 0 || pos >= mPlayListLen) {
522                 // The saved playlist is bogus, discard it
523                 mPlayListLen = 0;
524                 return;
525             }
526             mPlayPos = pos;
527 
528             // When reloadQueue is called in response to a card-insertion,
529             // we might not be able to query the media provider right away.
530             // To deal with this, try querying for the current file, and if
531             // that fails, wait a while and try again. If that too fails,
532             // assume there is a problem and don't restore the state.
533             Cursor crsr = MusicUtils.query(this,
534                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
535                         new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
536             if (crsr == null || crsr.getCount() == 0) {
537                 // wait a bit and try again
538                 SystemClock.sleep(3000);
539                 crsr = getContentResolver().query(
540                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
541                         mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
542             }
543             if (crsr != null) {
544                 crsr.close();
545             }
546 
547             // Make sure we don't auto-skip to the next song, since that
548             // also starts playback. What could happen in that case is:
549             // - music is paused
550             // - go to UMS and delete some files, including the currently playing one
551             // - come back from UMS
552             // (time passes)
553             // - music app is killed for some reason (out of memory)
554             // - music service is restarted, service restores state, doesn't find
555             //   the "current" file, goes to the next and: playback starts on its
556             //   own, potentially at some random inconvenient time.
557             mOpenFailedCounter = 20;
558             mQuietMode = true;
559             openCurrentAndNext();
560             mQuietMode = false;
561             if (!mPlayer.isInitialized()) {
562                 // couldn't restore the saved state
563                 mPlayListLen = 0;
564                 return;
565             }
566 
567             long seekpos = mPreferences.getLong("seekpos", 0);
568             seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
569             Log.d(LOGTAG, "restored queue, currently at position "
570                     + position() + "/" + duration()
571                     + " (requested " + seekpos + ")");
572 
573             int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
574             if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
575                 repmode = REPEAT_NONE;
576             }
577             mRepeatMode = repmode;
578 
579             int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
580             if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
581                 shufmode = SHUFFLE_NONE;
582             }
583             if (shufmode != SHUFFLE_NONE) {
584                 // in shuffle mode we need to restore the history too
585                 q = mPreferences.getString("history", "");
586                 qlen = q != null ? q.length() : 0;
587                 if (qlen > 1) {
588                     plen = 0;
589                     n = 0;
590                     shift = 0;
591                     mHistory.clear();
592                     for (int i = 0; i < qlen; i++) {
593                         char c = q.charAt(i);
594                         if (c == ';') {
595                             if (n >= mPlayListLen) {
596                                 // bogus history data
597                                 mHistory.clear();
598                                 break;
599                             }
600                             mHistory.add(n);
601                             n = 0;
602                             shift = 0;
603                         } else {
604                             if (c >= '0' && c <= '9') {
605                                 n += ((c - '0') << shift);
606                             } else if (c >= 'a' && c <= 'f') {
607                                 n += ((10 + c - 'a') << shift);
608                             } else {
609                                 // bogus history data
610                                 mHistory.clear();
611                                 break;
612                             }
613                             shift += 4;
614                         }
615                     }
616                 }
617             }
618             if (shufmode == SHUFFLE_AUTO) {
619                 if (! makeAutoShuffleList()) {
620                     shufmode = SHUFFLE_NONE;
621                 }
622             }
623             mShuffleMode = shufmode;
624         }
625     }
626 
627     @Override
onBind(Intent intent)628     public IBinder onBind(Intent intent) {
629         mDelayedStopHandler.removeCallbacksAndMessages(null);
630         mServiceInUse = true;
631         return mBinder;
632     }
633 
634     @Override
onRebind(Intent intent)635     public void onRebind(Intent intent) {
636         mDelayedStopHandler.removeCallbacksAndMessages(null);
637         mServiceInUse = true;
638     }
639 
640     @Override
onStartCommand(Intent intent, int flags, int startId)641     public int onStartCommand(Intent intent, int flags, int startId) {
642         mServiceStartId = startId;
643         mDelayedStopHandler.removeCallbacksAndMessages(null);
644 
645         if (intent != null) {
646             String action = intent.getAction();
647             String cmd = intent.getStringExtra("command");
648             MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
649 
650             if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
651                 gotoNext(true);
652             } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
653                 if (position() < 2000) {
654                     prev();
655                 } else {
656                     seek(0);
657                     play();
658                 }
659             } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
660                 if (isPlaying()) {
661                     pause();
662                     mPausedByTransientLossOfFocus = false;
663                 } else {
664                     play();
665                 }
666             } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
667                 pause();
668                 mPausedByTransientLossOfFocus = false;
669             } else if (CMDPLAY.equals(cmd)) {
670                 play();
671             } else if (CMDSTOP.equals(cmd)) {
672                 pause();
673                 mPausedByTransientLossOfFocus = false;
674                 seek(0);
675             }
676         }
677 
678         // make sure the service will shut down on its own if it was
679         // just started but not bound to and nothing is playing
680         mDelayedStopHandler.removeCallbacksAndMessages(null);
681         Message msg = mDelayedStopHandler.obtainMessage();
682         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
683         return START_STICKY;
684     }
685 
686     @Override
onUnbind(Intent intent)687     public boolean onUnbind(Intent intent) {
688         mServiceInUse = false;
689 
690         // Take a snapshot of the current playlist
691         saveQueue(true);
692 
693         if (isPlaying() || mPausedByTransientLossOfFocus) {
694             // something is currently playing, or will be playing once
695             // an in-progress action requesting audio focus ends, so don't stop the service now.
696             return true;
697         }
698 
699         // If there is a playlist but playback is paused, then wait a while
700         // before stopping the service, so that pause/resume isn't slow.
701         // Also delay stopping the service if we're transitioning between tracks.
702         if (mPlayListLen > 0  || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
703             Message msg = mDelayedStopHandler.obtainMessage();
704             mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
705             return true;
706         }
707 
708         // No active playlist, OK to stop the service right now
709         stopSelf(mServiceStartId);
710         return true;
711     }
712 
713     private Handler mDelayedStopHandler = new Handler() {
714         @Override
715         public void handleMessage(Message msg) {
716             // Check again to make sure nothing is playing right now
717             if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
718                     || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
719                 return;
720             }
721             // save the queue again, because it might have changed
722             // since the user exited the music app (because of
723             // party-shuffle or because the play-position changed)
724             saveQueue(true);
725             stopSelf(mServiceStartId);
726         }
727     };
728 
729     /**
730      * Called when we receive a ACTION_MEDIA_EJECT notification.
731      *
732      * @param storagePath path to mount point for the removed media
733      */
closeExternalStorageFiles(String storagePath)734     public void closeExternalStorageFiles(String storagePath) {
735         // stop playback and clean up if the SD card is going to be unmounted.
736         stop(true);
737         notifyChange(QUEUE_CHANGED);
738         notifyChange(META_CHANGED);
739     }
740 
741     /**
742      * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
743      * The intent will call closeExternalStorageFiles() if the external media
744      * is going to be ejected, so applications can clean up any files they have open.
745      */
registerExternalStorageListener()746     public void registerExternalStorageListener() {
747         if (mUnmountReceiver == null) {
748             mUnmountReceiver = new BroadcastReceiver() {
749                 @Override
750                 public void onReceive(Context context, Intent intent) {
751                     String action = intent.getAction();
752                     if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
753                         saveQueue(true);
754                         mQueueIsSaveable = false;
755                         closeExternalStorageFiles(intent.getData().getPath());
756                     } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
757                         mMediaMountedCount++;
758                         mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
759                         reloadQueue();
760                         mQueueIsSaveable = true;
761                         notifyChange(QUEUE_CHANGED);
762                         notifyChange(META_CHANGED);
763                     }
764                 }
765             };
766             IntentFilter iFilter = new IntentFilter();
767             iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
768             iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
769             iFilter.addDataScheme("file");
770             registerReceiver(mUnmountReceiver, iFilter);
771         }
772     }
773 
774     /**
775      * Notify the change-receivers that something has changed.
776      * The intent that is sent contains the following data
777      * for the currently playing track:
778      * "id" - Integer: the database row ID
779      * "artist" - String: the name of the artist
780      * "album" - String: the name of the album
781      * "track" - String: the name of the track
782      * The intent has an action that is one of
783      * "com.android.music.metachanged"
784      * "com.android.music.queuechanged",
785      * "com.android.music.playbackcomplete"
786      * "com.android.music.playstatechanged"
787      * respectively indicating that a new track has
788      * started playing, that the playback queue has
789      * changed, that playback has stopped because
790      * the last file in the list has been played,
791      * or that the play-state changed (paused/resumed).
792      */
notifyChange(String what)793     private void notifyChange(String what) {
794 
795         Intent i = new Intent(what);
796         i.putExtra("id", Long.valueOf(getAudioId()));
797         i.putExtra("artist", getArtistName());
798         i.putExtra("album",getAlbumName());
799         i.putExtra("track", getTrackName());
800         i.putExtra("playing", isPlaying());
801         sendStickyBroadcast(i);
802 
803         if (what.equals(PLAYSTATE_CHANGED)) {
804             mRemoteControlClient.setPlaybackState(isPlaying() ?
805                     RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
806         } else if (what.equals(META_CHANGED)) {
807             RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
808             ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
809             ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
810             ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
811             ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
812             Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
813             if (b != null) {
814                 ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
815             }
816             ed.apply();
817         }
818 
819         if (what.equals(QUEUE_CHANGED)) {
820             saveQueue(true);
821         } else {
822             saveQueue(false);
823         }
824 
825         // Share this notification directly with our widgets
826         mAppWidgetProvider.notifyChange(this, what);
827     }
828 
ensurePlayListCapacity(int size)829     private void ensurePlayListCapacity(int size) {
830         if (mPlayList == null || size > mPlayList.length) {
831             // reallocate at 2x requested size so we don't
832             // need to grow and copy the array for every
833             // insert
834             long [] newlist = new long[size * 2];
835             int len = mPlayList != null ? mPlayList.length : mPlayListLen;
836             for (int i = 0; i < len; i++) {
837                 newlist[i] = mPlayList[i];
838             }
839             mPlayList = newlist;
840         }
841         // FIXME: shrink the array when the needed size is much smaller
842         // than the allocated size
843     }
844 
845     // insert the list of songs at the specified position in the playlist
addToPlayList(long [] list, int position)846     private void addToPlayList(long [] list, int position) {
847         int addlen = list.length;
848         if (position < 0) { // overwrite
849             mPlayListLen = 0;
850             position = 0;
851         }
852         ensurePlayListCapacity(mPlayListLen + addlen);
853         if (position > mPlayListLen) {
854             position = mPlayListLen;
855         }
856 
857         // move part of list after insertion point
858         int tailsize = mPlayListLen - position;
859         for (int i = tailsize ; i > 0 ; i--) {
860             mPlayList[position + i] = mPlayList[position + i - addlen];
861         }
862 
863         // copy list into playlist
864         for (int i = 0; i < addlen; i++) {
865             mPlayList[position + i] = list[i];
866         }
867         mPlayListLen += addlen;
868         if (mPlayListLen == 0) {
869             mCursor.close();
870             mCursor = null;
871             notifyChange(META_CHANGED);
872         }
873     }
874 
875     /**
876      * Appends a list of tracks to the current playlist.
877      * If nothing is playing currently, playback will be started at
878      * the first track.
879      * If the action is NOW, playback will switch to the first of
880      * the new tracks immediately.
881      * @param list The list of tracks to append.
882      * @param action NOW, NEXT or LAST
883      */
enqueue(long [] list, int action)884     public void enqueue(long [] list, int action) {
885         synchronized(this) {
886             if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
887                 addToPlayList(list, mPlayPos + 1);
888                 notifyChange(QUEUE_CHANGED);
889             } else {
890                 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
891                 addToPlayList(list, Integer.MAX_VALUE);
892                 notifyChange(QUEUE_CHANGED);
893                 if (action == NOW) {
894                     mPlayPos = mPlayListLen - list.length;
895                     openCurrentAndNext();
896                     play();
897                     notifyChange(META_CHANGED);
898                     return;
899                 }
900             }
901             if (mPlayPos < 0) {
902                 mPlayPos = 0;
903                 openCurrentAndNext();
904                 play();
905                 notifyChange(META_CHANGED);
906             }
907         }
908     }
909 
910     /**
911      * Replaces the current playlist with a new list,
912      * and prepares for starting playback at the specified
913      * position in the list, or a random position if the
914      * specified position is 0.
915      * @param list The new list of tracks.
916      */
open(long [] list, int position)917     public void open(long [] list, int position) {
918         synchronized (this) {
919             if (mShuffleMode == SHUFFLE_AUTO) {
920                 mShuffleMode = SHUFFLE_NORMAL;
921             }
922             long oldId = getAudioId();
923             int listlength = list.length;
924             boolean newlist = true;
925             if (mPlayListLen == listlength) {
926                 // possible fast path: list might be the same
927                 newlist = false;
928                 for (int i = 0; i < listlength; i++) {
929                     if (list[i] != mPlayList[i]) {
930                         newlist = true;
931                         break;
932                     }
933                 }
934             }
935             if (newlist) {
936                 addToPlayList(list, -1);
937                 notifyChange(QUEUE_CHANGED);
938             }
939             int oldpos = mPlayPos;
940             if (position >= 0) {
941                 mPlayPos = position;
942             } else {
943                 mPlayPos = mRand.nextInt(mPlayListLen);
944             }
945             mHistory.clear();
946 
947             saveBookmarkIfNeeded();
948             openCurrentAndNext();
949             if (oldId != getAudioId()) {
950                 notifyChange(META_CHANGED);
951             }
952         }
953     }
954 
955     /**
956      * Moves the item at index1 to index2.
957      * @param index1
958      * @param index2
959      */
moveQueueItem(int index1, int index2)960     public void moveQueueItem(int index1, int index2) {
961         synchronized (this) {
962             if (index1 >= mPlayListLen) {
963                 index1 = mPlayListLen - 1;
964             }
965             if (index2 >= mPlayListLen) {
966                 index2 = mPlayListLen - 1;
967             }
968             if (index1 < index2) {
969                 long tmp = mPlayList[index1];
970                 for (int i = index1; i < index2; i++) {
971                     mPlayList[i] = mPlayList[i+1];
972                 }
973                 mPlayList[index2] = tmp;
974                 if (mPlayPos == index1) {
975                     mPlayPos = index2;
976                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
977                         mPlayPos--;
978                 }
979             } else if (index2 < index1) {
980                 long tmp = mPlayList[index1];
981                 for (int i = index1; i > index2; i--) {
982                     mPlayList[i] = mPlayList[i-1];
983                 }
984                 mPlayList[index2] = tmp;
985                 if (mPlayPos == index1) {
986                     mPlayPos = index2;
987                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
988                         mPlayPos++;
989                 }
990             }
991             notifyChange(QUEUE_CHANGED);
992         }
993     }
994 
995     /**
996      * Returns the current play list
997      * @return An array of integers containing the IDs of the tracks in the play list
998      */
getQueue()999     public long [] getQueue() {
1000         synchronized (this) {
1001             int len = mPlayListLen;
1002             long [] list = new long[len];
1003             for (int i = 0; i < len; i++) {
1004                 list[i] = mPlayList[i];
1005             }
1006             return list;
1007         }
1008     }
1009 
getCursorForId(long lid)1010     private Cursor getCursorForId(long lid) {
1011         String id = String.valueOf(lid);
1012 
1013         Cursor c = getContentResolver().query(
1014                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1015                 mCursorCols, "_id=" + id , null, null);
1016         if (c != null) {
1017             c.moveToFirst();
1018         }
1019         return c;
1020     }
1021 
openCurrentAndNext()1022     private void openCurrentAndNext() {
1023         synchronized (this) {
1024             if (mCursor != null) {
1025                 mCursor.close();
1026                 mCursor = null;
1027             }
1028 
1029             if (mPlayListLen == 0) {
1030                 return;
1031             }
1032             stop(false);
1033 
1034             mCursor = getCursorForId(mPlayList[mPlayPos]);
1035             while(true) {
1036                 if (mCursor != null && mCursor.getCount() != 0 &&
1037                         open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" +
1038                                 mCursor.getLong(IDCOLIDX))) {
1039                     break;
1040                 }
1041                 // if we get here then opening the file failed. We can close the cursor now, because
1042                 // we're either going to create a new one next, or stop trying
1043                 if (mCursor != null) {
1044                     mCursor.close();
1045                     mCursor = null;
1046                 }
1047                 if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
1048                     int pos = getNextPosition(false);
1049                     if (pos < 0) {
1050                         gotoIdleState();
1051                         if (mIsSupposedToBePlaying) {
1052                             mIsSupposedToBePlaying = false;
1053                             notifyChange(PLAYSTATE_CHANGED);
1054                         }
1055                         return;
1056                     }
1057                     mPlayPos = pos;
1058                     stop(false);
1059                     mPlayPos = pos;
1060                     mCursor = getCursorForId(mPlayList[mPlayPos]);
1061                 } else {
1062                     mOpenFailedCounter = 0;
1063                     if (!mQuietMode) {
1064                         Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1065                     }
1066                     Log.d(LOGTAG, "Failed to open file for playback");
1067                     gotoIdleState();
1068                     if (mIsSupposedToBePlaying) {
1069                         mIsSupposedToBePlaying = false;
1070                         notifyChange(PLAYSTATE_CHANGED);
1071                     }
1072                     return;
1073                 }
1074             }
1075 
1076             // go to bookmark if needed
1077             if (isPodcast()) {
1078                 long bookmark = getBookmark();
1079                 // Start playing a little bit before the bookmark,
1080                 // so it's easier to get back in to the narrative.
1081                 seek(bookmark - 5000);
1082             }
1083             setNextTrack();
1084         }
1085     }
1086 
setNextTrack()1087     private void setNextTrack() {
1088         mNextPlayPos = getNextPosition(false);
1089         if (mNextPlayPos >= 0) {
1090             long id = mPlayList[mNextPlayPos];
1091             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1092         } else {
1093             mPlayer.setNextDataSource(null);
1094         }
1095     }
1096 
1097     /**
1098      * Opens the specified file and readies it for playback.
1099      *
1100      * @param path The full path of the file to be opened.
1101      */
open(String path)1102     public boolean open(String path) {
1103         synchronized (this) {
1104             if (path == null) {
1105                 return false;
1106             }
1107 
1108             // if mCursor is null, try to associate path with a database cursor
1109             if (mCursor == null) {
1110 
1111                 ContentResolver resolver = getContentResolver();
1112                 Uri uri;
1113                 String where;
1114                 String selectionArgs[];
1115                 if (path.startsWith("content://media/")) {
1116                     uri = Uri.parse(path);
1117                     where = null;
1118                     selectionArgs = null;
1119                 } else {
1120                    uri = MediaStore.Audio.Media.getContentUriForPath(path);
1121                    where = MediaStore.Audio.Media.DATA + "=?";
1122                    selectionArgs = new String[] { path };
1123                 }
1124 
1125                 try {
1126                     mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1127                     if  (mCursor != null) {
1128                         if (mCursor.getCount() == 0) {
1129                             mCursor.close();
1130                             mCursor = null;
1131                         } else {
1132                             mCursor.moveToNext();
1133                             ensurePlayListCapacity(1);
1134                             mPlayListLen = 1;
1135                             mPlayList[0] = mCursor.getLong(IDCOLIDX);
1136                             mPlayPos = 0;
1137                         }
1138                     }
1139                 } catch (UnsupportedOperationException ex) {
1140                 }
1141             }
1142             mFileToPlay = path;
1143             mPlayer.setDataSource(mFileToPlay);
1144             if (mPlayer.isInitialized()) {
1145                 mOpenFailedCounter = 0;
1146                 return true;
1147             }
1148             stop(true);
1149             return false;
1150         }
1151     }
1152 
1153     /**
1154      * Starts playback of a previously opened file.
1155      */
play()1156     public void play() {
1157         mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1158                 AudioManager.AUDIOFOCUS_GAIN);
1159         mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1160                 MediaButtonIntentReceiver.class.getName()));
1161 
1162         if (mPlayer.isInitialized()) {
1163             // if we are at the end of the song, go to the next song first
1164             long duration = mPlayer.duration();
1165             if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1166                 mPlayer.position() >= duration - 2000) {
1167                 gotoNext(true);
1168             }
1169 
1170             mPlayer.start();
1171             // make sure we fade in, in case a previous fadein was stopped because
1172             // of another focus loss
1173             mMediaplayerHandler.removeMessages(FADEDOWN);
1174             mMediaplayerHandler.sendEmptyMessage(FADEUP);
1175 
1176             updateNotification();
1177             if (!mIsSupposedToBePlaying) {
1178                 mIsSupposedToBePlaying = true;
1179                 notifyChange(PLAYSTATE_CHANGED);
1180             }
1181 
1182         } else if (mPlayListLen <= 0) {
1183             // This is mostly so that if you press 'play' on a bluetooth headset
1184             // without every having played anything before, it will still play
1185             // something.
1186             setShuffleMode(SHUFFLE_AUTO);
1187         }
1188     }
1189 
updateNotification()1190     private void updateNotification() {
1191         RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1192         views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1193         if (getAudioId() < 0) {
1194             // streaming
1195             views.setTextViewText(R.id.trackname, getPath());
1196             views.setTextViewText(R.id.artistalbum, null);
1197         } else {
1198             String artist = getArtistName();
1199             views.setTextViewText(R.id.trackname, getTrackName());
1200             if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1201                 artist = getString(R.string.unknown_artist_name);
1202             }
1203             String album = getAlbumName();
1204             if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1205                 album = getString(R.string.unknown_album_name);
1206             }
1207 
1208             views.setTextViewText(R.id.artistalbum,
1209                     getString(R.string.notification_artist_album, artist, album)
1210                     );
1211         }
1212         Notification status = new Notification();
1213         status.contentView = views;
1214         status.flags |= Notification.FLAG_ONGOING_EVENT;
1215         status.icon = R.drawable.stat_notify_musicplayer;
1216         status.contentIntent = PendingIntent.getActivity(this, 0,
1217                 new Intent("com.android.music.PLAYBACK_VIEWER")
1218                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1219         startForeground(PLAYBACKSERVICE_STATUS, status);
1220     }
1221 
stop(boolean remove_status_icon)1222     private void stop(boolean remove_status_icon) {
1223         if (mPlayer != null && mPlayer.isInitialized()) {
1224             mPlayer.stop();
1225         }
1226         mFileToPlay = null;
1227         if (mCursor != null) {
1228             mCursor.close();
1229             mCursor = null;
1230         }
1231         if (remove_status_icon) {
1232             gotoIdleState();
1233         } else {
1234             stopForeground(false);
1235         }
1236         if (remove_status_icon) {
1237             mIsSupposedToBePlaying = false;
1238         }
1239     }
1240 
1241     /**
1242      * Stops playback.
1243      */
stop()1244     public void stop() {
1245         stop(true);
1246     }
1247 
1248     /**
1249      * Pauses playback (call play() to resume)
1250      */
pause()1251     public void pause() {
1252         synchronized(this) {
1253             mMediaplayerHandler.removeMessages(FADEUP);
1254             if (isPlaying()) {
1255                 mPlayer.pause();
1256                 gotoIdleState();
1257                 mIsSupposedToBePlaying = false;
1258                 notifyChange(PLAYSTATE_CHANGED);
1259                 saveBookmarkIfNeeded();
1260             }
1261         }
1262     }
1263 
1264     /** Returns whether something is currently playing
1265      *
1266      * @return true if something is playing (or will be playing shortly, in case
1267      * we're currently transitioning between tracks), false if not.
1268      */
isPlaying()1269     public boolean isPlaying() {
1270         return mIsSupposedToBePlaying;
1271     }
1272 
1273     /*
1274       Desired behavior for prev/next/shuffle:
1275 
1276       - NEXT will move to the next track in the list when not shuffling, and to
1277         a track randomly picked from the not-yet-played tracks when shuffling.
1278         If all tracks have already been played, pick from the full set, but
1279         avoid picking the previously played track if possible.
1280       - when shuffling, PREV will go to the previously played track. Hitting PREV
1281         again will go to the track played before that, etc. When the start of the
1282         history has been reached, PREV is a no-op.
1283         When not shuffling, PREV will go to the sequentially previous track (the
1284         difference with the shuffle-case is mainly that when not shuffling, the
1285         user can back up to tracks that are not in the history).
1286 
1287         Example:
1288         When playing an album with 10 tracks from the start, and enabling shuffle
1289         while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1290         the final play order might be 1-2-3-4-5-8-10-6-9-7.
1291         When hitting 'prev' 8 times while playing track 7 in this example, the
1292         user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1293         a random track will be picked again. If at any time user disables shuffling
1294         the next/previous track will be picked in sequential order again.
1295      */
1296 
prev()1297     public void prev() {
1298         synchronized (this) {
1299             if (mShuffleMode == SHUFFLE_NORMAL) {
1300                 // go to previously-played track and remove it from the history
1301                 int histsize = mHistory.size();
1302                 if (histsize == 0) {
1303                     // prev is a no-op
1304                     return;
1305                 }
1306                 Integer pos = mHistory.remove(histsize - 1);
1307                 mPlayPos = pos.intValue();
1308             } else {
1309                 if (mPlayPos > 0) {
1310                     mPlayPos--;
1311                 } else {
1312                     mPlayPos = mPlayListLen - 1;
1313                 }
1314             }
1315             saveBookmarkIfNeeded();
1316             stop(false);
1317             openCurrentAndNext();
1318             play();
1319             notifyChange(META_CHANGED);
1320         }
1321     }
1322 
1323     /**
1324      * Get the next position to play. Note that this may actually modify mPlayPos
1325      * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1326      * be adjusted. Either way, the return value is the next value that should be
1327      * assigned to mPlayPos;
1328      */
getNextPosition(boolean force)1329     private int getNextPosition(boolean force) {
1330         if (mRepeatMode == REPEAT_CURRENT) {
1331             if (mPlayPos < 0) return 0;
1332             return mPlayPos;
1333         } else if (mShuffleMode == SHUFFLE_NORMAL) {
1334             // Pick random next track from the not-yet-played ones
1335             // TODO: make it work right after adding/removing items in the queue.
1336 
1337             // Store the current file in the history, but keep the history at a
1338             // reasonable size
1339             if (mPlayPos >= 0) {
1340                 mHistory.add(mPlayPos);
1341             }
1342             if (mHistory.size() > MAX_HISTORY_SIZE) {
1343                 mHistory.removeElementAt(0);
1344             }
1345 
1346             int numTracks = mPlayListLen;
1347             int[] tracks = new int[numTracks];
1348             for (int i=0;i < numTracks; i++) {
1349                 tracks[i] = i;
1350             }
1351 
1352             int numHistory = mHistory.size();
1353             int numUnplayed = numTracks;
1354             for (int i=0;i < numHistory; i++) {
1355                 int idx = mHistory.get(i).intValue();
1356                 if (idx < numTracks && tracks[idx] >= 0) {
1357                     numUnplayed--;
1358                     tracks[idx] = -1;
1359                 }
1360             }
1361 
1362             // 'numUnplayed' now indicates how many tracks have not yet
1363             // been played, and 'tracks' contains the indices of those
1364             // tracks.
1365             if (numUnplayed <=0) {
1366                 // everything's already been played
1367                 if (mRepeatMode == REPEAT_ALL || force) {
1368                     //pick from full set
1369                     numUnplayed = numTracks;
1370                     for (int i=0;i < numTracks; i++) {
1371                         tracks[i] = i;
1372                     }
1373                 } else {
1374                     // all done
1375                     return -1;
1376                 }
1377             }
1378             int skip = mRand.nextInt(numUnplayed);
1379             int cnt = -1;
1380             while (true) {
1381                 while (tracks[++cnt] < 0)
1382                     ;
1383                 skip--;
1384                 if (skip < 0) {
1385                     break;
1386                 }
1387             }
1388             return cnt;
1389         } else if (mShuffleMode == SHUFFLE_AUTO) {
1390             doAutoShuffleUpdate();
1391             return mPlayPos + 1;
1392         } else {
1393             if (mPlayPos >= mPlayListLen - 1) {
1394                 // we're at the end of the list
1395                 if (mRepeatMode == REPEAT_NONE && !force) {
1396                     // all done
1397                     return -1;
1398                 } else if (mRepeatMode == REPEAT_ALL || force) {
1399                     return 0;
1400                 }
1401                 return -1;
1402             } else {
1403                 return mPlayPos + 1;
1404             }
1405         }
1406     }
1407 
gotoNext(boolean force)1408     public void gotoNext(boolean force) {
1409         synchronized (this) {
1410             if (mPlayListLen <= 0) {
1411                 Log.d(LOGTAG, "No play queue");
1412                 return;
1413             }
1414 
1415             int pos = getNextPosition(force);
1416             if (pos < 0) {
1417                 gotoIdleState();
1418                 if (mIsSupposedToBePlaying) {
1419                     mIsSupposedToBePlaying = false;
1420                     notifyChange(PLAYSTATE_CHANGED);
1421                 }
1422                 return;
1423             }
1424             mPlayPos = pos;
1425             saveBookmarkIfNeeded();
1426             stop(false);
1427             mPlayPos = pos;
1428             openCurrentAndNext();
1429             play();
1430             notifyChange(META_CHANGED);
1431         }
1432     }
1433 
gotoIdleState()1434     private void gotoIdleState() {
1435         mDelayedStopHandler.removeCallbacksAndMessages(null);
1436         Message msg = mDelayedStopHandler.obtainMessage();
1437         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1438         stopForeground(true);
1439     }
1440 
saveBookmarkIfNeeded()1441     private void saveBookmarkIfNeeded() {
1442         try {
1443             if (isPodcast()) {
1444                 long pos = position();
1445                 long bookmark = getBookmark();
1446                 long duration = duration();
1447                 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1448                         (pos > bookmark && (pos - 10000) < bookmark)) {
1449                     // The existing bookmark is close to the current
1450                     // position, so don't update it.
1451                     return;
1452                 }
1453                 if (pos < 15000 || (pos + 10000) > duration) {
1454                     // if we're near the start or end, clear the bookmark
1455                     pos = 0;
1456                 }
1457 
1458                 // write 'pos' to the bookmark field
1459                 ContentValues values = new ContentValues();
1460                 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1461                 Uri uri = ContentUris.withAppendedId(
1462                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1463                 getContentResolver().update(uri, values, null, null);
1464             }
1465         } catch (SQLiteException ex) {
1466         }
1467     }
1468 
1469     // Make sure there are at least 5 items after the currently playing item
1470     // and no more than 10 items before.
doAutoShuffleUpdate()1471     private void doAutoShuffleUpdate() {
1472         boolean notify = false;
1473 
1474         // remove old entries
1475         if (mPlayPos > 10) {
1476             removeTracks(0, mPlayPos - 9);
1477             notify = true;
1478         }
1479         // add new entries if needed
1480         int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1481         for (int i = 0; i < to_add; i++) {
1482             // pick something at random from the list
1483 
1484             int lookback = mHistory.size();
1485             int idx = -1;
1486             while(true) {
1487                 idx = mRand.nextInt(mAutoShuffleList.length);
1488                 if (!wasRecentlyUsed(idx, lookback)) {
1489                     break;
1490                 }
1491                 lookback /= 2;
1492             }
1493             mHistory.add(idx);
1494             if (mHistory.size() > MAX_HISTORY_SIZE) {
1495                 mHistory.remove(0);
1496             }
1497             ensurePlayListCapacity(mPlayListLen + 1);
1498             mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
1499             notify = true;
1500         }
1501         if (notify) {
1502             notifyChange(QUEUE_CHANGED);
1503         }
1504     }
1505 
1506     // check that the specified idx is not in the history (but only look at at
1507     // most lookbacksize entries in the history)
wasRecentlyUsed(int idx, int lookbacksize)1508     private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1509 
1510         // early exit to prevent infinite loops in case idx == mPlayPos
1511         if (lookbacksize == 0) {
1512             return false;
1513         }
1514 
1515         int histsize = mHistory.size();
1516         if (histsize < lookbacksize) {
1517             Log.d(LOGTAG, "lookback too big");
1518             lookbacksize = histsize;
1519         }
1520         int maxidx = histsize - 1;
1521         for (int i = 0; i < lookbacksize; i++) {
1522             long entry = mHistory.get(maxidx - i);
1523             if (entry == idx) {
1524                 return true;
1525             }
1526         }
1527         return false;
1528     }
1529 
1530     // A simple variation of Random that makes sure that the
1531     // value it returns is not equal to the value it returned
1532     // previously, unless the interval is 1.
1533     private static class Shuffler {
1534         private int mPrevious;
1535         private Random mRandom = new Random();
nextInt(int interval)1536         public int nextInt(int interval) {
1537             int ret;
1538             do {
1539                 ret = mRandom.nextInt(interval);
1540             } while (ret == mPrevious && interval > 1);
1541             mPrevious = ret;
1542             return ret;
1543         }
1544     };
1545 
makeAutoShuffleList()1546     private boolean makeAutoShuffleList() {
1547         ContentResolver res = getContentResolver();
1548         Cursor c = null;
1549         try {
1550             c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1551                     new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1552                     null, null);
1553             if (c == null || c.getCount() == 0) {
1554                 return false;
1555             }
1556             int len = c.getCount();
1557             long [] list = new long[len];
1558             for (int i = 0; i < len; i++) {
1559                 c.moveToNext();
1560                 list[i] = c.getLong(0);
1561             }
1562             mAutoShuffleList = list;
1563             return true;
1564         } catch (RuntimeException ex) {
1565         } finally {
1566             if (c != null) {
1567                 c.close();
1568             }
1569         }
1570         return false;
1571     }
1572 
1573     /**
1574      * Removes the range of tracks specified from the play list. If a file within the range is
1575      * the file currently being played, playback will move to the next file after the
1576      * range.
1577      * @param first The first file to be removed
1578      * @param last The last file to be removed
1579      * @return the number of tracks deleted
1580      */
removeTracks(int first, int last)1581     public int removeTracks(int first, int last) {
1582         int numremoved = removeTracksInternal(first, last);
1583         if (numremoved > 0) {
1584             notifyChange(QUEUE_CHANGED);
1585         }
1586         return numremoved;
1587     }
1588 
removeTracksInternal(int first, int last)1589     private int removeTracksInternal(int first, int last) {
1590         synchronized (this) {
1591             if (last < first) return 0;
1592             if (first < 0) first = 0;
1593             if (last >= mPlayListLen) last = mPlayListLen - 1;
1594 
1595             boolean gotonext = false;
1596             if (first <= mPlayPos && mPlayPos <= last) {
1597                 mPlayPos = first;
1598                 gotonext = true;
1599             } else if (mPlayPos > last) {
1600                 mPlayPos -= (last - first + 1);
1601             }
1602             int num = mPlayListLen - last - 1;
1603             for (int i = 0; i < num; i++) {
1604                 mPlayList[first + i] = mPlayList[last + 1 + i];
1605             }
1606             mPlayListLen -= last - first + 1;
1607 
1608             if (gotonext) {
1609                 if (mPlayListLen == 0) {
1610                     stop(true);
1611                     mPlayPos = -1;
1612                     if (mCursor != null) {
1613                         mCursor.close();
1614                         mCursor = null;
1615                     }
1616                 } else {
1617                     if (mPlayPos >= mPlayListLen) {
1618                         mPlayPos = 0;
1619                     }
1620                     boolean wasPlaying = isPlaying();
1621                     stop(false);
1622                     openCurrentAndNext();
1623                     if (wasPlaying) {
1624                         play();
1625                     }
1626                 }
1627                 notifyChange(META_CHANGED);
1628             }
1629             return last - first + 1;
1630         }
1631     }
1632 
1633     /**
1634      * Removes all instances of the track with the given id
1635      * from the playlist.
1636      * @param id The id to be removed
1637      * @return how many instances of the track were removed
1638      */
removeTrack(long id)1639     public int removeTrack(long id) {
1640         int numremoved = 0;
1641         synchronized (this) {
1642             for (int i = 0; i < mPlayListLen; i++) {
1643                 if (mPlayList[i] == id) {
1644                     numremoved += removeTracksInternal(i, i);
1645                     i--;
1646                 }
1647             }
1648         }
1649         if (numremoved > 0) {
1650             notifyChange(QUEUE_CHANGED);
1651         }
1652         return numremoved;
1653     }
1654 
setShuffleMode(int shufflemode)1655     public void setShuffleMode(int shufflemode) {
1656         synchronized(this) {
1657             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1658                 return;
1659             }
1660             mShuffleMode = shufflemode;
1661             if (mShuffleMode == SHUFFLE_AUTO) {
1662                 if (makeAutoShuffleList()) {
1663                     mPlayListLen = 0;
1664                     doAutoShuffleUpdate();
1665                     mPlayPos = 0;
1666                     openCurrentAndNext();
1667                     play();
1668                     notifyChange(META_CHANGED);
1669                     return;
1670                 } else {
1671                     // failed to build a list of files to shuffle
1672                     mShuffleMode = SHUFFLE_NONE;
1673                 }
1674             }
1675             saveQueue(false);
1676         }
1677     }
getShuffleMode()1678     public int getShuffleMode() {
1679         return mShuffleMode;
1680     }
1681 
setRepeatMode(int repeatmode)1682     public void setRepeatMode(int repeatmode) {
1683         synchronized(this) {
1684             mRepeatMode = repeatmode;
1685             setNextTrack();
1686             saveQueue(false);
1687         }
1688     }
getRepeatMode()1689     public int getRepeatMode() {
1690         return mRepeatMode;
1691     }
1692 
getMediaMountedCount()1693     public int getMediaMountedCount() {
1694         return mMediaMountedCount;
1695     }
1696 
1697     /**
1698      * Returns the path of the currently playing file, or null if
1699      * no file is currently playing.
1700      */
getPath()1701     public String getPath() {
1702         return mFileToPlay;
1703     }
1704 
1705     /**
1706      * Returns the rowid of the currently playing file, or -1 if
1707      * no file is currently playing.
1708      */
getAudioId()1709     public long getAudioId() {
1710         synchronized (this) {
1711             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1712                 return mPlayList[mPlayPos];
1713             }
1714         }
1715         return -1;
1716     }
1717 
1718     /**
1719      * Returns the position in the queue
1720      * @return the position in the queue
1721      */
getQueuePosition()1722     public int getQueuePosition() {
1723         synchronized(this) {
1724             return mPlayPos;
1725         }
1726     }
1727 
1728     /**
1729      * Starts playing the track at the given position in the queue.
1730      * @param pos The position in the queue of the track that will be played.
1731      */
setQueuePosition(int pos)1732     public void setQueuePosition(int pos) {
1733         synchronized(this) {
1734             stop(false);
1735             mPlayPos = pos;
1736             openCurrentAndNext();
1737             play();
1738             notifyChange(META_CHANGED);
1739             if (mShuffleMode == SHUFFLE_AUTO) {
1740                 doAutoShuffleUpdate();
1741             }
1742         }
1743     }
1744 
getArtistName()1745     public String getArtistName() {
1746         synchronized(this) {
1747             if (mCursor == null) {
1748                 return null;
1749             }
1750             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1751         }
1752     }
1753 
getArtistId()1754     public long getArtistId() {
1755         synchronized (this) {
1756             if (mCursor == null) {
1757                 return -1;
1758             }
1759             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1760         }
1761     }
1762 
getAlbumName()1763     public String getAlbumName() {
1764         synchronized (this) {
1765             if (mCursor == null) {
1766                 return null;
1767             }
1768             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1769         }
1770     }
1771 
getAlbumId()1772     public long getAlbumId() {
1773         synchronized (this) {
1774             if (mCursor == null) {
1775                 return -1;
1776             }
1777             return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1778         }
1779     }
1780 
getTrackName()1781     public String getTrackName() {
1782         synchronized (this) {
1783             if (mCursor == null) {
1784                 return null;
1785             }
1786             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1787         }
1788     }
1789 
isPodcast()1790     private boolean isPodcast() {
1791         synchronized (this) {
1792             if (mCursor == null) {
1793                 return false;
1794             }
1795             return (mCursor.getInt(PODCASTCOLIDX) > 0);
1796         }
1797     }
1798 
getBookmark()1799     private long getBookmark() {
1800         synchronized (this) {
1801             if (mCursor == null) {
1802                 return 0;
1803             }
1804             return mCursor.getLong(BOOKMARKCOLIDX);
1805         }
1806     }
1807 
1808     /**
1809      * Returns the duration of the file in milliseconds.
1810      * Currently this method returns -1 for the duration of MIDI files.
1811      */
duration()1812     public long duration() {
1813         if (mPlayer.isInitialized()) {
1814             return mPlayer.duration();
1815         }
1816         return -1;
1817     }
1818 
1819     /**
1820      * Returns the current playback position in milliseconds
1821      */
position()1822     public long position() {
1823         if (mPlayer.isInitialized()) {
1824             return mPlayer.position();
1825         }
1826         return -1;
1827     }
1828 
1829     /**
1830      * Seeks to the position specified.
1831      *
1832      * @param pos The position to seek to, in milliseconds
1833      */
seek(long pos)1834     public long seek(long pos) {
1835         if (mPlayer.isInitialized()) {
1836             if (pos < 0) pos = 0;
1837             if (pos > mPlayer.duration()) pos = mPlayer.duration();
1838             return mPlayer.seek(pos);
1839         }
1840         return -1;
1841     }
1842 
1843     /**
1844      * Sets the audio session ID.
1845      *
1846      * @param sessionId: the audio session ID.
1847      */
setAudioSessionId(int sessionId)1848     public void setAudioSessionId(int sessionId) {
1849         synchronized (this) {
1850             mPlayer.setAudioSessionId(sessionId);
1851         }
1852     }
1853 
1854     /**
1855      * Returns the audio session ID.
1856      */
getAudioSessionId()1857     public int getAudioSessionId() {
1858         synchronized (this) {
1859             return mPlayer.getAudioSessionId();
1860         }
1861     }
1862 
1863     /**
1864      * Provides a unified interface for dealing with midi files and
1865      * other media files.
1866      */
1867     private class MultiPlayer {
1868         private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1869         private CompatMediaPlayer mNextMediaPlayer;
1870         private Handler mHandler;
1871         private boolean mIsInitialized = false;
1872 
MultiPlayer()1873         public MultiPlayer() {
1874             mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1875         }
1876 
setDataSource(String path)1877         public void setDataSource(String path) {
1878             mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1879             if (mIsInitialized) {
1880                 setNextDataSource(null);
1881             }
1882         }
1883 
setDataSourceImpl(MediaPlayer player, String path)1884         private boolean setDataSourceImpl(MediaPlayer player, String path) {
1885             try {
1886                 player.reset();
1887                 player.setOnPreparedListener(null);
1888                 if (path.startsWith("content://")) {
1889                     player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1890                 } else {
1891                     player.setDataSource(path);
1892                 }
1893                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1894                 player.prepare();
1895             } catch (IOException ex) {
1896                 // TODO: notify the user why the file couldn't be opened
1897                 return false;
1898             } catch (IllegalArgumentException ex) {
1899                 // TODO: notify the user why the file couldn't be opened
1900                 return false;
1901             }
1902             player.setOnCompletionListener(listener);
1903             player.setOnErrorListener(errorListener);
1904             Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1905             i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1906             i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1907             sendBroadcast(i);
1908             return true;
1909         }
1910 
setNextDataSource(String path)1911         public void setNextDataSource(String path) {
1912             mCurrentMediaPlayer.setNextMediaPlayer(null);
1913             if (mNextMediaPlayer != null) {
1914                 mNextMediaPlayer.release();
1915                 mNextMediaPlayer = null;
1916             }
1917             if (path == null) {
1918                 return;
1919             }
1920             mNextMediaPlayer = new CompatMediaPlayer();
1921             mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1922             mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
1923             if (setDataSourceImpl(mNextMediaPlayer, path)) {
1924                 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1925             } else {
1926                 // failed to open next, we'll transition the old fashioned way,
1927                 // which will skip over the faulty file
1928                 mNextMediaPlayer.release();
1929                 mNextMediaPlayer = null;
1930             }
1931         }
1932 
isInitialized()1933         public boolean isInitialized() {
1934             return mIsInitialized;
1935         }
1936 
start()1937         public void start() {
1938             MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
1939             mCurrentMediaPlayer.start();
1940         }
1941 
stop()1942         public void stop() {
1943             mCurrentMediaPlayer.reset();
1944             mIsInitialized = false;
1945         }
1946 
1947         /**
1948          * You CANNOT use this player anymore after calling release()
1949          */
release()1950         public void release() {
1951             stop();
1952             mCurrentMediaPlayer.release();
1953         }
1954 
pause()1955         public void pause() {
1956             mCurrentMediaPlayer.pause();
1957         }
1958 
setHandler(Handler handler)1959         public void setHandler(Handler handler) {
1960             mHandler = handler;
1961         }
1962 
1963         MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1964             public void onCompletion(MediaPlayer mp) {
1965                 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1966                     mCurrentMediaPlayer.release();
1967                     mCurrentMediaPlayer = mNextMediaPlayer;
1968                     mNextMediaPlayer = null;
1969                     mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1970                 } else {
1971                     // Acquire a temporary wakelock, since when we return from
1972                     // this callback the MediaPlayer will release its wakelock
1973                     // and allow the device to go to sleep.
1974                     // This temporary wakelock is released when the RELEASE_WAKELOCK
1975                     // message is processed, but just in case, put a timeout on it.
1976                     mWakeLock.acquire(30000);
1977                     mHandler.sendEmptyMessage(TRACK_ENDED);
1978                     mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1979                 }
1980             }
1981         };
1982 
1983         MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1984             public boolean onError(MediaPlayer mp, int what, int extra) {
1985                 switch (what) {
1986                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1987                     mIsInitialized = false;
1988                     mCurrentMediaPlayer.release();
1989                     // Creating a new MediaPlayer and settings its wakemode does not
1990                     // require the media service, so it's OK to do this now, while the
1991                     // service is still being restarted
1992                     mCurrentMediaPlayer = new CompatMediaPlayer();
1993                     mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1994                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1995                     return true;
1996                 default:
1997                     Log.d("MultiPlayer", "Error: " + what + "," + extra);
1998                     break;
1999                 }
2000                 return false;
2001            }
2002         };
2003 
duration()2004         public long duration() {
2005             return mCurrentMediaPlayer.getDuration();
2006         }
2007 
position()2008         public long position() {
2009             return mCurrentMediaPlayer.getCurrentPosition();
2010         }
2011 
seek(long whereto)2012         public long seek(long whereto) {
2013             mCurrentMediaPlayer.seekTo((int) whereto);
2014             return whereto;
2015         }
2016 
setVolume(float vol)2017         public void setVolume(float vol) {
2018             mCurrentMediaPlayer.setVolume(vol, vol);
2019         }
2020 
setAudioSessionId(int sessionId)2021         public void setAudioSessionId(int sessionId) {
2022             mCurrentMediaPlayer.setAudioSessionId(sessionId);
2023         }
2024 
getAudioSessionId()2025         public int getAudioSessionId() {
2026             return mCurrentMediaPlayer.getAudioSessionId();
2027         }
2028     }
2029 
2030     static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2031 
2032         private boolean mCompatMode = true;
2033         private MediaPlayer mNextPlayer;
2034         private OnCompletionListener mCompletion;
2035 
CompatMediaPlayer()2036         public CompatMediaPlayer() {
2037             try {
2038                 MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2039                 mCompatMode = false;
2040             } catch (NoSuchMethodException e) {
2041                 mCompatMode = true;
2042                 super.setOnCompletionListener(this);
2043             }
2044         }
2045 
setNextMediaPlayer(MediaPlayer next)2046         public void setNextMediaPlayer(MediaPlayer next) {
2047             if (mCompatMode) {
2048                 mNextPlayer = next;
2049             } else {
2050                 super.setNextMediaPlayer(next);
2051             }
2052         }
2053 
2054         @Override
setOnCompletionListener(OnCompletionListener listener)2055         public void setOnCompletionListener(OnCompletionListener listener) {
2056             if (mCompatMode) {
2057                 mCompletion = listener;
2058             } else {
2059                 super.setOnCompletionListener(listener);
2060             }
2061         }
2062 
2063         @Override
onCompletion(MediaPlayer mp)2064         public void onCompletion(MediaPlayer mp) {
2065             if (mNextPlayer != null) {
2066                 // as it turns out, starting a new MediaPlayer on the completion
2067                 // of a previous player ends up slightly overlapping the two
2068                 // playbacks, so slightly delaying the start of the next player
2069                 // gives a better user experience
2070                 SystemClock.sleep(50);
2071                 mNextPlayer.start();
2072             }
2073             mCompletion.onCompletion(this);
2074         }
2075     }
2076 
2077     /*
2078      * By making this a static class with a WeakReference to the Service, we
2079      * ensure that the Service can be GCd even when the system process still
2080      * has a remote reference to the stub.
2081      */
2082     static class ServiceStub extends IMediaPlaybackService.Stub {
2083         WeakReference<MediaPlaybackService> mService;
2084 
ServiceStub(MediaPlaybackService service)2085         ServiceStub(MediaPlaybackService service) {
2086             mService = new WeakReference<MediaPlaybackService>(service);
2087         }
2088 
openFile(String path)2089         public void openFile(String path)
2090         {
2091             mService.get().open(path);
2092         }
open(long [] list, int position)2093         public void open(long [] list, int position) {
2094             mService.get().open(list, position);
2095         }
getQueuePosition()2096         public int getQueuePosition() {
2097             return mService.get().getQueuePosition();
2098         }
setQueuePosition(int index)2099         public void setQueuePosition(int index) {
2100             mService.get().setQueuePosition(index);
2101         }
isPlaying()2102         public boolean isPlaying() {
2103             return mService.get().isPlaying();
2104         }
stop()2105         public void stop() {
2106             mService.get().stop();
2107         }
pause()2108         public void pause() {
2109             mService.get().pause();
2110         }
play()2111         public void play() {
2112             mService.get().play();
2113         }
prev()2114         public void prev() {
2115             mService.get().prev();
2116         }
next()2117         public void next() {
2118             mService.get().gotoNext(true);
2119         }
getTrackName()2120         public String getTrackName() {
2121             return mService.get().getTrackName();
2122         }
getAlbumName()2123         public String getAlbumName() {
2124             return mService.get().getAlbumName();
2125         }
getAlbumId()2126         public long getAlbumId() {
2127             return mService.get().getAlbumId();
2128         }
getArtistName()2129         public String getArtistName() {
2130             return mService.get().getArtistName();
2131         }
getArtistId()2132         public long getArtistId() {
2133             return mService.get().getArtistId();
2134         }
enqueue(long [] list , int action)2135         public void enqueue(long [] list , int action) {
2136             mService.get().enqueue(list, action);
2137         }
getQueue()2138         public long [] getQueue() {
2139             return mService.get().getQueue();
2140         }
moveQueueItem(int from, int to)2141         public void moveQueueItem(int from, int to) {
2142             mService.get().moveQueueItem(from, to);
2143         }
getPath()2144         public String getPath() {
2145             return mService.get().getPath();
2146         }
getAudioId()2147         public long getAudioId() {
2148             return mService.get().getAudioId();
2149         }
position()2150         public long position() {
2151             return mService.get().position();
2152         }
duration()2153         public long duration() {
2154             return mService.get().duration();
2155         }
seek(long pos)2156         public long seek(long pos) {
2157             return mService.get().seek(pos);
2158         }
setShuffleMode(int shufflemode)2159         public void setShuffleMode(int shufflemode) {
2160             mService.get().setShuffleMode(shufflemode);
2161         }
getShuffleMode()2162         public int getShuffleMode() {
2163             return mService.get().getShuffleMode();
2164         }
removeTracks(int first, int last)2165         public int removeTracks(int first, int last) {
2166             return mService.get().removeTracks(first, last);
2167         }
removeTrack(long id)2168         public int removeTrack(long id) {
2169             return mService.get().removeTrack(id);
2170         }
setRepeatMode(int repeatmode)2171         public void setRepeatMode(int repeatmode) {
2172             mService.get().setRepeatMode(repeatmode);
2173         }
getRepeatMode()2174         public int getRepeatMode() {
2175             return mService.get().getRepeatMode();
2176         }
getMediaMountedCount()2177         public int getMediaMountedCount() {
2178             return mService.get().getMediaMountedCount();
2179         }
getAudioSessionId()2180         public int getAudioSessionId() {
2181             return mService.get().getAudioSessionId();
2182         }
2183     }
2184 
2185     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)2186     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
2187         writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
2188         writer.println("Currently loaded:");
2189         writer.println(getArtistName());
2190         writer.println(getAlbumName());
2191         writer.println(getTrackName());
2192         writer.println(getPath());
2193         writer.println("playing: " + mIsSupposedToBePlaying);
2194         writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
2195         writer.println("shuffle mode: " + mShuffleMode);
2196         MusicUtils.debugDump(writer);
2197     }
2198 
2199     private final IBinder mBinder = new ServiceStub(this);
2200 }
2201