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