OSDN Git Service

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