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