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