OSDN Git Service

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