OSDN Git Service

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