OSDN Git Service

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