OSDN Git Service

6095c0696c23ca50da33efbe5ea0475b2d7a1663
[android-x86/packages-apps-Music.git] / src / com / android / music / MediaPlaybackService.java
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     
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
265     public MediaPlaybackService() {
266     }
267
268     @Override
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
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
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
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
502     public IBinder onBind(Intent intent) {
503         mDelayedStopHandler.removeCallbacksAndMessages(null);
504         mServiceInUse = true;
505         return mBinder;
506     }
507
508     @Override
509     public void onRebind(Intent intent) {
510         mDelayedStopHandler.removeCallbacksAndMessages(null);
511         mServiceInUse = true;
512     }
513
514     @Override
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
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      */
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      */
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      */
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
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
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      */
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      */
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      */
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      */
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
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
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      */
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      */
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             if (mRepeatMode != REPEAT_CURRENT &&
980                 mPlayer.position() >= mPlayer.duration() - 2000) {
981                 next(true);
982             }
983
984             mPlayer.start();
985             setForeground(true);
986
987             NotificationManager nm = (NotificationManager)
988             getSystemService(Context.NOTIFICATION_SERVICE);
989     
990             RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
991             views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
992             if (getAudioId() < 0) {
993                 // streaming
994                 views.setTextViewText(R.id.trackname, getPath());
995                 views.setTextViewText(R.id.artistalbum, null);
996             } else {
997                 String artist = getArtistName();
998                 views.setTextViewText(R.id.trackname, getTrackName());
999                 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
1000                     artist = getString(R.string.unknown_artist_name);
1001                 }
1002                 String album = getAlbumName();
1003                 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1004                     album = getString(R.string.unknown_album_name);
1005                 }
1006                 
1007                 views.setTextViewText(R.id.artistalbum,
1008                         getString(R.string.notification_artist_album, artist, album)
1009                         );
1010             }
1011             
1012             Notification status = new Notification();
1013             status.contentView = views;
1014             status.flags |= Notification.FLAG_ONGOING_EVENT;
1015             status.icon = R.drawable.stat_notify_musicplayer;
1016             status.contentIntent = PendingIntent.getActivity(this, 0,
1017                     new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1018             nm.notify(PLAYBACKSERVICE_STATUS, status);
1019             if (!mIsSupposedToBePlaying) {
1020                 notifyChange(PLAYSTATE_CHANGED);
1021             }
1022             mIsSupposedToBePlaying = true;
1023         } else if (mPlayListLen <= 0) {
1024             // This is mostly so that if you press 'play' on a bluetooth headset
1025             // without every having played anything before, it will still play
1026             // something.
1027             setShuffleMode(SHUFFLE_AUTO);
1028         }
1029     }
1030     
1031     private void stop(boolean remove_status_icon) {
1032         if (mPlayer.isInitialized()) {
1033             mPlayer.stop();
1034         }
1035         mFileToPlay = null;
1036         if (mCursor != null) {
1037             mCursor.close();
1038             mCursor = null;
1039         }
1040         if (remove_status_icon) {
1041             gotoIdleState();
1042         }
1043         setForeground(false);
1044         if (remove_status_icon) {
1045             mIsSupposedToBePlaying = false;
1046         }
1047     }
1048
1049     /**
1050      * Stops playback.
1051      */
1052     public void stop() {
1053         stop(true);
1054     }
1055
1056     /**
1057      * Pauses playback (call play() to resume)
1058      */
1059     public void pause() {
1060         synchronized(this) {
1061             if (isPlaying()) {
1062                 mPlayer.pause();
1063                 gotoIdleState();
1064                 setForeground(false);
1065                 mIsSupposedToBePlaying = false;
1066                 notifyChange(PLAYSTATE_CHANGED);
1067                 saveBookmarkIfNeeded();
1068             }
1069         }
1070     }
1071
1072     /** Returns whether something is currently playing
1073      *
1074      * @return true if something is playing (or will be playing shortly, in case
1075      * we're currently transitioning between tracks), false if not.
1076      */
1077     public boolean isPlaying() {
1078         return mIsSupposedToBePlaying;
1079     }
1080
1081     /*
1082       Desired behavior for prev/next/shuffle:
1083
1084       - NEXT will move to the next track in the list when not shuffling, and to
1085         a track randomly picked from the not-yet-played tracks when shuffling.
1086         If all tracks have already been played, pick from the full set, but
1087         avoid picking the previously played track if possible.
1088       - when shuffling, PREV will go to the previously played track. Hitting PREV
1089         again will go to the track played before that, etc. When the start of the
1090         history has been reached, PREV is a no-op.
1091         When not shuffling, PREV will go to the sequentially previous track (the
1092         difference with the shuffle-case is mainly that when not shuffling, the
1093         user can back up to tracks that are not in the history).
1094
1095         Example:
1096         When playing an album with 10 tracks from the start, and enabling shuffle
1097         while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1098         the final play order might be 1-2-3-4-5-8-10-6-9-7.
1099         When hitting 'prev' 8 times while playing track 7 in this example, the
1100         user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1101         a random track will be picked again. If at any time user disables shuffling
1102         the next/previous track will be picked in sequential order again.
1103      */
1104
1105     public void prev() {
1106         synchronized (this) {
1107             if (mOneShot) {
1108                 // we were playing a specific file not part of a playlist, so there is no 'previous'
1109                 seek(0);
1110                 play();
1111                 return;
1112             }
1113             if (mShuffleMode == SHUFFLE_NORMAL) {
1114                 // go to previously-played track and remove it from the history
1115                 int histsize = mHistory.size();
1116                 if (histsize == 0) {
1117                     // prev is a no-op
1118                     return;
1119                 }
1120                 Integer pos = mHistory.remove(histsize - 1);
1121                 mPlayPos = pos.intValue();
1122             } else {
1123                 if (mPlayPos > 0) {
1124                     mPlayPos--;
1125                 } else {
1126                     mPlayPos = mPlayListLen - 1;
1127                 }
1128             }
1129             saveBookmarkIfNeeded();
1130             stop(false);
1131             openCurrent();
1132             play();
1133             notifyChange(META_CHANGED);
1134         }
1135     }
1136
1137     public void next(boolean force) {
1138         synchronized (this) {
1139             if (mOneShot) {
1140                 // we were playing a specific file not part of a playlist, so there is no 'next'
1141                 seek(0);
1142                 play();
1143                 return;
1144             }
1145
1146             if (mPlayListLen <= 0) {
1147                 return;
1148             }
1149
1150             // Store the current file in the history, but keep the history at a
1151             // reasonable size
1152             if (mPlayPos >= 0) {
1153                 mHistory.add(Integer.valueOf(mPlayPos));
1154             }
1155             if (mHistory.size() > MAX_HISTORY_SIZE) {
1156                 mHistory.removeElementAt(0);
1157             }
1158
1159             if (mShuffleMode == SHUFFLE_NORMAL) {
1160                 // Pick random next track from the not-yet-played ones
1161                 // TODO: make it work right after adding/removing items in the queue.
1162
1163                 int numTracks = mPlayListLen;
1164                 int[] tracks = new int[numTracks];
1165                 for (int i=0;i < numTracks; i++) {
1166                     tracks[i] = i;
1167                 }
1168
1169                 int numHistory = mHistory.size();
1170                 int numUnplayed = numTracks;
1171                 for (int i=0;i < numHistory; i++) {
1172                     int idx = mHistory.get(i).intValue();
1173                     if (idx < numTracks && tracks[idx] >= 0) {
1174                         numUnplayed--;
1175                         tracks[idx] = -1;
1176                     }
1177                 }
1178
1179                 // 'numUnplayed' now indicates how many tracks have not yet
1180                 // been played, and 'tracks' contains the indices of those
1181                 // tracks.
1182                 if (numUnplayed <=0) {
1183                     // everything's already been played
1184                     if (mRepeatMode == REPEAT_ALL || force) {
1185                         //pick from full set
1186                         numUnplayed = numTracks;
1187                         for (int i=0;i < numTracks; i++) {
1188                             tracks[i] = i;
1189                         }
1190                     } else {
1191                         // all done
1192                         gotoIdleState();
1193                         return;
1194                     }
1195                 }
1196                 int skip = mRand.nextInt(numUnplayed);
1197                 int cnt = -1;
1198                 while (true) {
1199                     while (tracks[++cnt] < 0)
1200                         ;
1201                     skip--;
1202                     if (skip < 0) {
1203                         break;
1204                     }
1205                 }
1206                 mPlayPos = cnt;
1207             } else if (mShuffleMode == SHUFFLE_AUTO) {
1208                 doAutoShuffleUpdate();
1209                 mPlayPos++;
1210             } else {
1211                 if (mPlayPos >= mPlayListLen - 1) {
1212                     // we're at the end of the list
1213                     if (mRepeatMode == REPEAT_NONE && !force) {
1214                         // all done
1215                         gotoIdleState();
1216                         notifyChange(PLAYBACK_COMPLETE);
1217                         mIsSupposedToBePlaying = false;
1218                         return;
1219                     } else if (mRepeatMode == REPEAT_ALL || force) {
1220                         mPlayPos = 0;
1221                     }
1222                 } else {
1223                     mPlayPos++;
1224                 }
1225             }
1226             saveBookmarkIfNeeded();
1227             stop(false);
1228             openCurrent();
1229             play();
1230             notifyChange(META_CHANGED);
1231         }
1232     }
1233     
1234     private void gotoIdleState() {
1235         NotificationManager nm =
1236             (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1237         nm.cancel(PLAYBACKSERVICE_STATUS);
1238         mDelayedStopHandler.removeCallbacksAndMessages(null);
1239         Message msg = mDelayedStopHandler.obtainMessage();
1240         mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1241     }
1242     
1243     private void saveBookmarkIfNeeded() {
1244         try {
1245             if (isPodcast()) {
1246                 long pos = position();
1247                 long bookmark = getBookmark();
1248                 long duration = duration();
1249                 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1250                         (pos > bookmark && (pos - 10000) < bookmark)) {
1251                     // The existing bookmark is close to the current
1252                     // position, so don't update it.
1253                     return;
1254                 }
1255                 if (pos < 15000 || (pos + 10000) > duration) {
1256                     // if we're near the start or end, clear the bookmark
1257                     pos = 0;
1258                 }
1259                 
1260                 // write 'pos' to the bookmark field
1261                 ContentValues values = new ContentValues();
1262                 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1263                 Uri uri = ContentUris.withAppendedId(
1264                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1265                 getContentResolver().update(uri, values, null, null);
1266             }
1267         } catch (SQLiteException ex) {
1268         }
1269     }
1270
1271     // Make sure there are at least 5 items after the currently playing item
1272     // and no more than 10 items before.
1273     private void doAutoShuffleUpdate() {
1274         boolean notify = false;
1275         // remove old entries
1276         if (mPlayPos > 10) {
1277             removeTracks(0, mPlayPos - 9);
1278             notify = true;
1279         }
1280         // add new entries if needed
1281         int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1282         for (int i = 0; i < to_add; i++) {
1283             // pick something at random from the list
1284             int idx = mRand.nextInt(mAutoShuffleList.length);
1285             Integer which = mAutoShuffleList[idx];
1286             ensurePlayListCapacity(mPlayListLen + 1);
1287             mPlayList[mPlayListLen++] = which;
1288             notify = true;
1289         }
1290         if (notify) {
1291             notifyChange(QUEUE_CHANGED);
1292         }
1293     }
1294
1295     // A simple variation of Random that makes sure that the
1296     // value it returns is not equal to the value it returned
1297     // previously, unless the interval is 1.
1298     private static class Shuffler {
1299         private int mPrevious;
1300         private Random mRandom = new Random();
1301         public int nextInt(int interval) {
1302             int ret;
1303             do {
1304                 ret = mRandom.nextInt(interval);
1305             } while (ret == mPrevious && interval > 1);
1306             mPrevious = ret;
1307             return ret;
1308         }
1309     };
1310
1311     private boolean makeAutoShuffleList() {
1312         ContentResolver res = getContentResolver();
1313         Cursor c = null;
1314         try {
1315             c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1316                     new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1317                     null, null);
1318             if (c == null || c.getCount() == 0) {
1319                 return false;
1320             }
1321             int len = c.getCount();
1322             int[] list = new int[len];
1323             for (int i = 0; i < len; i++) {
1324                 c.moveToNext();
1325                 list[i] = c.getInt(0);
1326             }
1327             mAutoShuffleList = list;
1328             return true;
1329         } catch (RuntimeException ex) {
1330         } finally {
1331             if (c != null) {
1332                 c.close();
1333             }
1334         }
1335         return false;
1336     }
1337     
1338     /**
1339      * Removes the range of tracks specified from the play list. If a file within the range is
1340      * the file currently being played, playback will move to the next file after the
1341      * range. 
1342      * @param first The first file to be removed
1343      * @param last The last file to be removed
1344      * @return the number of tracks deleted
1345      */
1346     public int removeTracks(int first, int last) {
1347         int numremoved = removeTracksInternal(first, last);
1348         if (numremoved > 0) {
1349             notifyChange(QUEUE_CHANGED);
1350         }
1351         return numremoved;
1352     }
1353     
1354     private int removeTracksInternal(int first, int last) {
1355         synchronized (this) {
1356             if (last < first) return 0;
1357             if (first < 0) first = 0;
1358             if (last >= mPlayListLen) last = mPlayListLen - 1;
1359
1360             boolean gotonext = false;
1361             if (first <= mPlayPos && mPlayPos <= last) {
1362                 mPlayPos = first;
1363                 gotonext = true;
1364             } else if (mPlayPos > last) {
1365                 mPlayPos -= (last - first + 1);
1366             }
1367             int num = mPlayListLen - last - 1;
1368             for (int i = 0; i < num; i++) {
1369                 mPlayList[first + i] = mPlayList[last + 1 + i];
1370             }
1371             mPlayListLen -= last - first + 1;
1372             
1373             if (gotonext) {
1374                 if (mPlayListLen == 0) {
1375                     stop(true);
1376                     mPlayPos = -1;
1377                 } else {
1378                     if (mPlayPos >= mPlayListLen) {
1379                         mPlayPos = 0;
1380                     }
1381                     boolean wasPlaying = isPlaying();
1382                     stop(false);
1383                     openCurrent();
1384                     if (wasPlaying) {
1385                         play();
1386                     }
1387                 }
1388             }
1389             return last - first + 1;
1390         }
1391     }
1392     
1393     /**
1394      * Removes all instances of the track with the given id
1395      * from the playlist.
1396      * @param id The id to be removed
1397      * @return how many instances of the track were removed
1398      */
1399     public int removeTrack(int id) {
1400         int numremoved = 0;
1401         synchronized (this) {
1402             for (int i = 0; i < mPlayListLen; i++) {
1403                 if (mPlayList[i] == id) {
1404                     numremoved += removeTracksInternal(i, i);
1405                     i--;
1406                 }
1407             }
1408         }
1409         if (numremoved > 0) {
1410             notifyChange(QUEUE_CHANGED);
1411         }
1412         return numremoved;
1413     }
1414     
1415     public void setShuffleMode(int shufflemode) {
1416         synchronized(this) {
1417             if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1418                 return;
1419             }
1420             mShuffleMode = shufflemode;
1421             if (mShuffleMode == SHUFFLE_AUTO) {
1422                 if (makeAutoShuffleList()) {
1423                     mPlayListLen = 0;
1424                     doAutoShuffleUpdate();
1425                     mPlayPos = 0;
1426                     openCurrent();
1427                     play();
1428                     notifyChange(META_CHANGED);
1429                     return;
1430                 } else {
1431                     // failed to build a list of files to shuffle
1432                     mShuffleMode = SHUFFLE_NONE;
1433                 }
1434             }
1435             saveQueue(false);
1436         }
1437     }
1438     public int getShuffleMode() {
1439         return mShuffleMode;
1440     }
1441     
1442     public void setRepeatMode(int repeatmode) {
1443         synchronized(this) {
1444             mRepeatMode = repeatmode;
1445             saveQueue(false);
1446         }
1447     }
1448     public int getRepeatMode() {
1449         return mRepeatMode;
1450     }
1451
1452     public int getMediaMountedCount() {
1453         return mMediaMountedCount;
1454     }
1455
1456     /**
1457      * Returns the path of the currently playing file, or null if
1458      * no file is currently playing.
1459      */
1460     public String getPath() {
1461         return mFileToPlay;
1462     }
1463     
1464     /**
1465      * Returns the rowid of the currently playing file, or -1 if
1466      * no file is currently playing.
1467      */
1468     public int getAudioId() {
1469         synchronized (this) {
1470             if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1471                 return mPlayList[mPlayPos];
1472             }
1473         }
1474         return -1;
1475     }
1476     
1477     /**
1478      * Returns the position in the queue 
1479      * @return the position in the queue
1480      */
1481     public int getQueuePosition() {
1482         synchronized(this) {
1483             return mPlayPos;
1484         }
1485     }
1486     
1487     /**
1488      * Starts playing the track at the given position in the queue.
1489      * @param pos The position in the queue of the track that will be played.
1490      */
1491     public void setQueuePosition(int pos) {
1492         synchronized(this) {
1493             stop(false);
1494             mPlayPos = pos;
1495             openCurrent();
1496             play();
1497             notifyChange(META_CHANGED);
1498         }
1499     }
1500
1501     public String getArtistName() {
1502         synchronized(this) {
1503             if (mCursor == null) {
1504                 return null;
1505             }
1506             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1507         }
1508     }
1509     
1510     public int getArtistId() {
1511         synchronized (this) {
1512             if (mCursor == null) {
1513                 return -1;
1514             }
1515             return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1516         }
1517     }
1518
1519     public String getAlbumName() {
1520         synchronized (this) {
1521             if (mCursor == null) {
1522                 return null;
1523             }
1524             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1525         }
1526     }
1527
1528     public int getAlbumId() {
1529         synchronized (this) {
1530             if (mCursor == null) {
1531                 return -1;
1532             }
1533             return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1534         }
1535     }
1536
1537     public String getTrackName() {
1538         synchronized (this) {
1539             if (mCursor == null) {
1540                 return null;
1541             }
1542             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1543         }
1544     }
1545
1546     private boolean isPodcast() {
1547         synchronized (this) {
1548             if (mCursor == null) {
1549                 return false;
1550             }
1551             return (mCursor.getInt(PODCASTCOLIDX) > 0);
1552         }
1553     }
1554     
1555     private long getBookmark() {
1556         synchronized (this) {
1557             if (mCursor == null) {
1558                 return 0;
1559             }
1560             return mCursor.getLong(BOOKMARKCOLIDX);
1561         }
1562     }
1563     
1564     /**
1565      * Returns the duration of the file in milliseconds.
1566      * Currently this method returns -1 for the duration of MIDI files.
1567      */
1568     public long duration() {
1569         if (mPlayer.isInitialized()) {
1570             return mPlayer.duration();
1571         }
1572         return -1;
1573     }
1574
1575     /**
1576      * Returns the current playback position in milliseconds
1577      */
1578     public long position() {
1579         if (mPlayer.isInitialized()) {
1580             return mPlayer.position();
1581         }
1582         return -1;
1583     }
1584
1585     /**
1586      * Seeks to the position specified.
1587      *
1588      * @param pos The position to seek to, in milliseconds
1589      */
1590     public long seek(long pos) {
1591         if (mPlayer.isInitialized()) {
1592             if (pos < 0) pos = 0;
1593             if (pos > mPlayer.duration()) pos = mPlayer.duration();
1594             return mPlayer.seek(pos);
1595         }
1596         return -1;
1597     }
1598
1599     /**
1600      * Provides a unified interface for dealing with midi files and
1601      * other media files.
1602      */
1603     private class MultiPlayer {
1604         private MediaPlayer mMediaPlayer = new MediaPlayer();
1605         private Handler mHandler;
1606         private boolean mIsInitialized = false;
1607
1608         public MultiPlayer() {
1609             mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1610         }
1611
1612         public void setDataSourceAsync(String path) {
1613             try {
1614                 mMediaPlayer.reset();
1615                 mMediaPlayer.setDataSource(path);
1616                 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1617                 mMediaPlayer.setOnPreparedListener(preparedlistener);
1618                 mMediaPlayer.prepareAsync();
1619             } catch (IOException ex) {
1620                 // TODO: notify the user why the file couldn't be opened
1621                 mIsInitialized = false;
1622                 return;
1623             } catch (IllegalArgumentException ex) {
1624                 // TODO: notify the user why the file couldn't be opened
1625                 mIsInitialized = false;
1626                 return;
1627             }
1628             mMediaPlayer.setOnCompletionListener(listener);
1629             mMediaPlayer.setOnErrorListener(errorListener);
1630             
1631             mIsInitialized = true;
1632         }
1633         
1634         public void setDataSource(String path) {
1635             try {
1636                 mMediaPlayer.reset();
1637                 mMediaPlayer.setOnPreparedListener(null);
1638                 if (path.startsWith("content://")) {
1639                     mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1640                 } else {
1641                     mMediaPlayer.setDataSource(path);
1642                 }
1643                 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1644                 mMediaPlayer.prepare();
1645             } catch (IOException ex) {
1646                 // TODO: notify the user why the file couldn't be opened
1647                 mIsInitialized = false;
1648                 return;
1649             } catch (IllegalArgumentException ex) {
1650                 // TODO: notify the user why the file couldn't be opened
1651                 mIsInitialized = false;
1652                 return;
1653             }
1654             mMediaPlayer.setOnCompletionListener(listener);
1655             mMediaPlayer.setOnErrorListener(errorListener);
1656             
1657             mIsInitialized = true;
1658         }
1659         
1660         public boolean isInitialized() {
1661             return mIsInitialized;
1662         }
1663
1664         public void start() {
1665             mMediaPlayer.start();
1666         }
1667
1668         public void stop() {
1669             mMediaPlayer.reset();
1670             mIsInitialized = false;
1671         }
1672
1673         /**
1674          * You CANNOT use this player anymore after calling release()
1675          */
1676         public void release() {
1677             stop();
1678             mMediaPlayer.release();
1679         }
1680         
1681         public void pause() {
1682             mMediaPlayer.pause();
1683         }
1684         
1685         public void setHandler(Handler handler) {
1686             mHandler = handler;
1687         }
1688
1689         MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1690             public void onCompletion(MediaPlayer mp) {
1691                 // Acquire a temporary wakelock, since when we return from
1692                 // this callback the MediaPlayer will release its wakelock
1693                 // and allow the device to go to sleep.
1694                 // This temporary wakelock is released when the RELEASE_WAKELOCK
1695                 // message is processed, but just in case, put a timeout on it.
1696                 mWakeLock.acquire(30000);
1697                 mHandler.sendEmptyMessage(TRACK_ENDED);
1698                 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1699             }
1700         };
1701
1702         MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1703             public void onPrepared(MediaPlayer mp) {
1704                 notifyChange(ASYNC_OPEN_COMPLETE);
1705             }
1706         };
1707  
1708         MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1709             public boolean onError(MediaPlayer mp, int what, int extra) {
1710                 switch (what) {
1711                 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1712                     mIsInitialized = false;
1713                     mMediaPlayer.release();
1714                     // Creating a new MediaPlayer and settings its wakemode does not
1715                     // require the media service, so it's OK to do this now, while the
1716                     // service is still being restarted
1717                     mMediaPlayer = new MediaPlayer(); 
1718                     mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1719                     mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1720                     return true;
1721                 default:
1722                     break;
1723                 }
1724                 return false;
1725            }
1726         };
1727
1728         public long duration() {
1729             return mMediaPlayer.getDuration();
1730         }
1731
1732         public long position() {
1733             return mMediaPlayer.getCurrentPosition();
1734         }
1735
1736         public long seek(long whereto) {
1737             mMediaPlayer.seekTo((int) whereto);
1738             return whereto;
1739         }
1740
1741         public void setVolume(float vol) {
1742             mMediaPlayer.setVolume(vol, vol);
1743         }
1744     }
1745
1746     /*
1747      * By making this a static class with a WeakReference to the Service, we
1748      * ensure that the Service can be GCd even when the system process still
1749      * has a remote reference to the stub.
1750      */
1751     static class ServiceStub extends IMediaPlaybackService.Stub {
1752         WeakReference<MediaPlaybackService> mService;
1753         
1754         ServiceStub(MediaPlaybackService service) {
1755             mService = new WeakReference<MediaPlaybackService>(service);
1756         }
1757
1758         public void openFileAsync(String path)
1759         {
1760             mService.get().openAsync(path);
1761         }
1762         public void openFile(String path, boolean oneShot)
1763         {
1764             mService.get().open(path, oneShot);
1765         }
1766         public void open(int [] list, int position) {
1767             mService.get().open(list, position);
1768         }
1769         public int getQueuePosition() {
1770             return mService.get().getQueuePosition();
1771         }
1772         public void setQueuePosition(int index) {
1773             mService.get().setQueuePosition(index);
1774         }
1775         public boolean isPlaying() {
1776             return mService.get().isPlaying();
1777         }
1778         public void stop() {
1779             mService.get().stop();
1780         }
1781         public void pause() {
1782             mService.get().pause();
1783         }
1784         public void play() {
1785             mService.get().play();
1786         }
1787         public void prev() {
1788             mService.get().prev();
1789         }
1790         public void next() {
1791             mService.get().next(true);
1792         }
1793         public String getTrackName() {
1794             return mService.get().getTrackName();
1795         }
1796         public String getAlbumName() {
1797             return mService.get().getAlbumName();
1798         }
1799         public int getAlbumId() {
1800             return mService.get().getAlbumId();
1801         }
1802         public String getArtistName() {
1803             return mService.get().getArtistName();
1804         }
1805         public int getArtistId() {
1806             return mService.get().getArtistId();
1807         }
1808         public void enqueue(int [] list , int action) {
1809             mService.get().enqueue(list, action);
1810         }
1811         public int [] getQueue() {
1812             return mService.get().getQueue();
1813         }
1814         public void moveQueueItem(int from, int to) {
1815             mService.get().moveQueueItem(from, to);
1816         }
1817         public String getPath() {
1818             return mService.get().getPath();
1819         }
1820         public int getAudioId() {
1821             return mService.get().getAudioId();
1822         }
1823         public long position() {
1824             return mService.get().position();
1825         }
1826         public long duration() {
1827             return mService.get().duration();
1828         }
1829         public long seek(long pos) {
1830             return mService.get().seek(pos);
1831         }
1832         public void setShuffleMode(int shufflemode) {
1833             mService.get().setShuffleMode(shufflemode);
1834         }
1835         public int getShuffleMode() {
1836             return mService.get().getShuffleMode();
1837         }
1838         public int removeTracks(int first, int last) {
1839             return mService.get().removeTracks(first, last);
1840         }
1841         public int removeTrack(int id) {
1842             return mService.get().removeTrack(id);
1843         }
1844         public void setRepeatMode(int repeatmode) {
1845             mService.get().setRepeatMode(repeatmode);
1846         }
1847         public int getRepeatMode() {
1848             return mService.get().getRepeatMode();
1849         }
1850         public int getMediaMountedCount() {
1851             return mService.get().getMediaMountedCount();
1852         }
1853
1854     }
1855     
1856     private final IBinder mBinder = new ServiceStub(this);
1857 }