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