OSDN Git Service

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