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