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        } else {
1093            mPlayer.setNextDataSource(null);
1094        }
1095    }
1096
1097    /**
1098     * Opens the specified file and readies it for playback.
1099     *
1100     * @param path The full path of the file to be opened.
1101     */
1102    public boolean open(String path) {
1103        synchronized (this) {
1104            if (path == null) {
1105                return false;
1106            }
1107
1108            // if mCursor is null, try to associate path with a database cursor
1109            if (mCursor == null) {
1110
1111                ContentResolver resolver = getContentResolver();
1112                Uri uri;
1113                String where;
1114                String selectionArgs[];
1115                if (path.startsWith("content://media/")) {
1116                    uri = Uri.parse(path);
1117                    where = null;
1118                    selectionArgs = null;
1119                } else {
1120                   uri = MediaStore.Audio.Media.getContentUriForPath(path);
1121                   where = MediaStore.Audio.Media.DATA + "=?";
1122                   selectionArgs = new String[] { path };
1123                }
1124
1125                try {
1126                    mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1127                    if  (mCursor != null) {
1128                        if (mCursor.getCount() == 0) {
1129                            mCursor.close();
1130                            mCursor = null;
1131                        } else {
1132                            mCursor.moveToNext();
1133                            ensurePlayListCapacity(1);
1134                            mPlayListLen = 1;
1135                            mPlayList[0] = mCursor.getLong(IDCOLIDX);
1136                            mPlayPos = 0;
1137                        }
1138                    }
1139                } catch (UnsupportedOperationException ex) {
1140                }
1141            }
1142            mFileToPlay = path;
1143            mPlayer.setDataSource(mFileToPlay);
1144            if (mPlayer.isInitialized()) {
1145                mOpenFailedCounter = 0;
1146                return true;
1147            }
1148            stop(true);
1149            return false;
1150        }
1151    }
1152
1153    /**
1154     * Starts playback of a previously opened file.
1155     */
1156    public void play() {
1157        mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1158                AudioManager.AUDIOFOCUS_GAIN);
1159        mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1160                MediaButtonIntentReceiver.class.getName()));
1161
1162        if (mPlayer.isInitialized()) {
1163            // if we are at the end of the song, go to the next song first
1164            long duration = mPlayer.duration();
1165            if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1166                mPlayer.position() >= duration - 2000) {
1167                gotoNext(true);
1168            }
1169
1170            mPlayer.start();
1171            // make sure we fade in, in case a previous fadein was stopped because
1172            // of another focus loss
1173            mMediaplayerHandler.removeMessages(FADEDOWN);
1174            mMediaplayerHandler.sendEmptyMessage(FADEUP);
1175
1176            updateNotification();
1177            if (!mIsSupposedToBePlaying) {
1178                mIsSupposedToBePlaying = true;
1179                notifyChange(PLAYSTATE_CHANGED);
1180            }
1181
1182        } else if (mPlayListLen <= 0) {
1183            // This is mostly so that if you press 'play' on a bluetooth headset
1184            // without every having played anything before, it will still play
1185            // something.
1186            setShuffleMode(SHUFFLE_AUTO);
1187        }
1188    }
1189
1190    private void updateNotification() {
1191        RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1192        views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1193        if (getAudioId() < 0) {
1194            // streaming
1195            views.setTextViewText(R.id.trackname, getPath());
1196            views.setTextViewText(R.id.artistalbum, null);
1197        } else {
1198            String artist = getArtistName();
1199            views.setTextViewText(R.id.trackname, getTrackName());
1200            if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1201                artist = getString(R.string.unknown_artist_name);
1202            }
1203            String album = getAlbumName();
1204            if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1205                album = getString(R.string.unknown_album_name);
1206            }
1207
1208            views.setTextViewText(R.id.artistalbum,
1209                    getString(R.string.notification_artist_album, artist, album)
1210                    );
1211        }
1212        Notification status = new Notification();
1213        status.contentView = views;
1214        status.flags |= Notification.FLAG_ONGOING_EVENT;
1215        status.icon = R.drawable.stat_notify_musicplayer;
1216        status.contentIntent = PendingIntent.getActivity(this, 0,
1217                new Intent("com.android.music.PLAYBACK_VIEWER")
1218                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1219        startForeground(PLAYBACKSERVICE_STATUS, status);
1220    }
1221
1222    private void stop(boolean remove_status_icon) {
1223        if (mPlayer != null && mPlayer.isInitialized()) {
1224            mPlayer.stop();
1225        }
1226        mFileToPlay = null;
1227        if (mCursor != null) {
1228            mCursor.close();
1229            mCursor = null;
1230        }
1231        if (remove_status_icon) {
1232            gotoIdleState();
1233        } else {
1234            stopForeground(false);
1235        }
1236        if (remove_status_icon) {
1237            mIsSupposedToBePlaying = false;
1238        }
1239    }
1240
1241    /**
1242     * Stops playback.
1243     */
1244    public void stop() {
1245        stop(true);
1246    }
1247
1248    /**
1249     * Pauses playback (call play() to resume)
1250     */
1251    public void pause() {
1252        synchronized(this) {
1253            mMediaplayerHandler.removeMessages(FADEUP);
1254            if (isPlaying()) {
1255                mPlayer.pause();
1256                gotoIdleState();
1257                mIsSupposedToBePlaying = false;
1258                notifyChange(PLAYSTATE_CHANGED);
1259                saveBookmarkIfNeeded();
1260            }
1261        }
1262    }
1263
1264    /** Returns whether something is currently playing
1265     *
1266     * @return true if something is playing (or will be playing shortly, in case
1267     * we're currently transitioning between tracks), false if not.
1268     */
1269    public boolean isPlaying() {
1270        return mIsSupposedToBePlaying;
1271    }
1272
1273    /*
1274      Desired behavior for prev/next/shuffle:
1275
1276      - NEXT will move to the next track in the list when not shuffling, and to
1277        a track randomly picked from the not-yet-played tracks when shuffling.
1278        If all tracks have already been played, pick from the full set, but
1279        avoid picking the previously played track if possible.
1280      - when shuffling, PREV will go to the previously played track. Hitting PREV
1281        again will go to the track played before that, etc. When the start of the
1282        history has been reached, PREV is a no-op.
1283        When not shuffling, PREV will go to the sequentially previous track (the
1284        difference with the shuffle-case is mainly that when not shuffling, the
1285        user can back up to tracks that are not in the history).
1286
1287        Example:
1288        When playing an album with 10 tracks from the start, and enabling shuffle
1289        while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1290        the final play order might be 1-2-3-4-5-8-10-6-9-7.
1291        When hitting 'prev' 8 times while playing track 7 in this example, the
1292        user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1293        a random track will be picked again. If at any time user disables shuffling
1294        the next/previous track will be picked in sequential order again.
1295     */
1296
1297    public void prev() {
1298        synchronized (this) {
1299            if (mShuffleMode == SHUFFLE_NORMAL) {
1300                // go to previously-played track and remove it from the history
1301                int histsize = mHistory.size();
1302                if (histsize == 0) {
1303                    // prev is a no-op
1304                    return;
1305                }
1306                Integer pos = mHistory.remove(histsize - 1);
1307                mPlayPos = pos.intValue();
1308            } else {
1309                if (mPlayPos > 0) {
1310                    mPlayPos--;
1311                } else {
1312                    mPlayPos = mPlayListLen - 1;
1313                }
1314            }
1315            saveBookmarkIfNeeded();
1316            stop(false);
1317            openCurrentAndNext();
1318            play();
1319            notifyChange(META_CHANGED);
1320        }
1321    }
1322
1323    /**
1324     * Get the next position to play. Note that this may actually modify mPlayPos
1325     * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1326     * be adjusted. Either way, the return value is the next value that should be
1327     * assigned to mPlayPos;
1328     */
1329    private int getNextPosition(boolean force) {
1330        if (mRepeatMode == REPEAT_CURRENT) {
1331            if (mPlayPos < 0) return 0;
1332            return mPlayPos;
1333        } else if (mShuffleMode == SHUFFLE_NORMAL) {
1334            // Pick random next track from the not-yet-played ones
1335            // TODO: make it work right after adding/removing items in the queue.
1336
1337            // Store the current file in the history, but keep the history at a
1338            // reasonable size
1339            if (mPlayPos >= 0) {
1340                mHistory.add(mPlayPos);
1341            }
1342            if (mHistory.size() > MAX_HISTORY_SIZE) {
1343                mHistory.removeElementAt(0);
1344            }
1345
1346            int numTracks = mPlayListLen;
1347            int[] tracks = new int[numTracks];
1348            for (int i=0;i < numTracks; i++) {
1349                tracks[i] = i;
1350            }
1351
1352            int numHistory = mHistory.size();
1353            int numUnplayed = numTracks;
1354            for (int i=0;i < numHistory; i++) {
1355                int idx = mHistory.get(i).intValue();
1356                if (idx < numTracks && tracks[idx] >= 0) {
1357                    numUnplayed--;
1358                    tracks[idx] = -1;
1359                }
1360            }
1361
1362            // 'numUnplayed' now indicates how many tracks have not yet
1363            // been played, and 'tracks' contains the indices of those
1364            // tracks.
1365            if (numUnplayed <=0) {
1366                // everything's already been played
1367                if (mRepeatMode == REPEAT_ALL || force) {
1368                    //pick from full set
1369                    numUnplayed = numTracks;
1370                    for (int i=0;i < numTracks; i++) {
1371                        tracks[i] = i;
1372                    }
1373                } else {
1374                    // all done
1375                    return -1;
1376                }
1377            }
1378            int skip = mRand.nextInt(numUnplayed);
1379            int cnt = -1;
1380            while (true) {
1381                while (tracks[++cnt] < 0)
1382                    ;
1383                skip--;
1384                if (skip < 0) {
1385                    break;
1386                }
1387            }
1388            return cnt;
1389        } else if (mShuffleMode == SHUFFLE_AUTO) {
1390            doAutoShuffleUpdate();
1391            return mPlayPos + 1;
1392        } else {
1393            if (mPlayPos >= mPlayListLen - 1) {
1394                // we're at the end of the list
1395                if (mRepeatMode == REPEAT_NONE && !force) {
1396                    // all done
1397                    return -1;
1398                } else if (mRepeatMode == REPEAT_ALL || force) {
1399                    return 0;
1400                }
1401                return -1;
1402            } else {
1403                return mPlayPos + 1;
1404            }
1405        }
1406    }
1407
1408    public void gotoNext(boolean force) {
1409        synchronized (this) {
1410            if (mPlayListLen <= 0) {
1411                Log.d(LOGTAG, "No play queue");
1412                return;
1413            }
1414
1415            int pos = getNextPosition(force);
1416            if (pos < 0) {
1417                gotoIdleState();
1418                if (mIsSupposedToBePlaying) {
1419                    mIsSupposedToBePlaying = false;
1420                    notifyChange(PLAYSTATE_CHANGED);
1421                }
1422                return;
1423            }
1424            mPlayPos = pos;
1425            saveBookmarkIfNeeded();
1426            stop(false);
1427            mPlayPos = pos;
1428            openCurrentAndNext();
1429            play();
1430            notifyChange(META_CHANGED);
1431        }
1432    }
1433
1434    private void gotoIdleState() {
1435        mDelayedStopHandler.removeCallbacksAndMessages(null);
1436        Message msg = mDelayedStopHandler.obtainMessage();
1437        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1438        stopForeground(true);
1439    }
1440
1441    private void saveBookmarkIfNeeded() {
1442        try {
1443            if (isPodcast()) {
1444                long pos = position();
1445                long bookmark = getBookmark();
1446                long duration = duration();
1447                if ((pos < bookmark && (pos + 10000) > bookmark) ||
1448                        (pos > bookmark && (pos - 10000) < bookmark)) {
1449                    // The existing bookmark is close to the current
1450                    // position, so don't update it.
1451                    return;
1452                }
1453                if (pos < 15000 || (pos + 10000) > duration) {
1454                    // if we're near the start or end, clear the bookmark
1455                    pos = 0;
1456                }
1457
1458                // write 'pos' to the bookmark field
1459                ContentValues values = new ContentValues();
1460                values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1461                Uri uri = ContentUris.withAppendedId(
1462                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1463                getContentResolver().update(uri, values, null, null);
1464            }
1465        } catch (SQLiteException ex) {
1466        }
1467    }
1468
1469    // Make sure there are at least 5 items after the currently playing item
1470    // and no more than 10 items before.
1471    private void doAutoShuffleUpdate() {
1472        boolean notify = false;
1473
1474        // remove old entries
1475        if (mPlayPos > 10) {
1476            removeTracks(0, mPlayPos - 9);
1477            notify = true;
1478        }
1479        // add new entries if needed
1480        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1481        for (int i = 0; i < to_add; i++) {
1482            // pick something at random from the list
1483
1484            int lookback = mHistory.size();
1485            int idx = -1;
1486            while(true) {
1487                idx = mRand.nextInt(mAutoShuffleList.length);
1488                if (!wasRecentlyUsed(idx, lookback)) {
1489                    break;
1490                }
1491                lookback /= 2;
1492            }
1493            mHistory.add(idx);
1494            if (mHistory.size() > MAX_HISTORY_SIZE) {
1495                mHistory.remove(0);
1496            }
1497            ensurePlayListCapacity(mPlayListLen + 1);
1498            mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
1499            notify = true;
1500        }
1501        if (notify) {
1502            notifyChange(QUEUE_CHANGED);
1503        }
1504    }
1505
1506    // check that the specified idx is not in the history (but only look at at
1507    // most lookbacksize entries in the history)
1508    private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1509
1510        // early exit to prevent infinite loops in case idx == mPlayPos
1511        if (lookbacksize == 0) {
1512            return false;
1513        }
1514
1515        int histsize = mHistory.size();
1516        if (histsize < lookbacksize) {
1517            Log.d(LOGTAG, "lookback too big");
1518            lookbacksize = histsize;
1519        }
1520        int maxidx = histsize - 1;
1521        for (int i = 0; i < lookbacksize; i++) {
1522            long entry = mHistory.get(maxidx - i);
1523            if (entry == idx) {
1524                return true;
1525            }
1526        }
1527        return false;
1528    }
1529
1530    // A simple variation of Random that makes sure that the
1531    // value it returns is not equal to the value it returned
1532    // previously, unless the interval is 1.
1533    private static class Shuffler {
1534        private int mPrevious;
1535        private Random mRandom = new Random();
1536        public int nextInt(int interval) {
1537            int ret;
1538            do {
1539                ret = mRandom.nextInt(interval);
1540            } while (ret == mPrevious && interval > 1);
1541            mPrevious = ret;
1542            return ret;
1543        }
1544    };
1545
1546    private boolean makeAutoShuffleList() {
1547        ContentResolver res = getContentResolver();
1548        Cursor c = null;
1549        try {
1550            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1551                    new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1552                    null, null);
1553            if (c == null || c.getCount() == 0) {
1554                return false;
1555            }
1556            int len = c.getCount();
1557            long [] list = new long[len];
1558            for (int i = 0; i < len; i++) {
1559                c.moveToNext();
1560                list[i] = c.getLong(0);
1561            }
1562            mAutoShuffleList = list;
1563            return true;
1564        } catch (RuntimeException ex) {
1565        } finally {
1566            if (c != null) {
1567                c.close();
1568            }
1569        }
1570        return false;
1571    }
1572
1573    /**
1574     * Removes the range of tracks specified from the play list. If a file within the range is
1575     * the file currently being played, playback will move to the next file after the
1576     * range.
1577     * @param first The first file to be removed
1578     * @param last The last file to be removed
1579     * @return the number of tracks deleted
1580     */
1581    public int removeTracks(int first, int last) {
1582        int numremoved = removeTracksInternal(first, last);
1583        if (numremoved > 0) {
1584            notifyChange(QUEUE_CHANGED);
1585        }
1586        return numremoved;
1587    }
1588
1589    private int removeTracksInternal(int first, int last) {
1590        synchronized (this) {
1591            if (last < first) return 0;
1592            if (first < 0) first = 0;
1593            if (last >= mPlayListLen) last = mPlayListLen - 1;
1594
1595            boolean gotonext = false;
1596            if (first <= mPlayPos && mPlayPos <= last) {
1597                mPlayPos = first;
1598                gotonext = true;
1599            } else if (mPlayPos > last) {
1600                mPlayPos -= (last - first + 1);
1601            }
1602            int num = mPlayListLen - last - 1;
1603            for (int i = 0; i < num; i++) {
1604                mPlayList[first + i] = mPlayList[last + 1 + i];
1605            }
1606            mPlayListLen -= last - first + 1;
1607
1608            if (gotonext) {
1609                if (mPlayListLen == 0) {
1610                    stop(true);
1611                    mPlayPos = -1;
1612                    if (mCursor != null) {
1613                        mCursor.close();
1614                        mCursor = null;
1615                    }
1616                } else {
1617                    if (mPlayPos >= mPlayListLen) {
1618                        mPlayPos = 0;
1619                    }
1620                    boolean wasPlaying = isPlaying();
1621                    stop(false);
1622                    openCurrentAndNext();
1623                    if (wasPlaying) {
1624                        play();
1625                    }
1626                }
1627                notifyChange(META_CHANGED);
1628            }
1629            return last - first + 1;
1630        }
1631    }
1632
1633    /**
1634     * Removes all instances of the track with the given id
1635     * from the playlist.
1636     * @param id The id to be removed
1637     * @return how many instances of the track were removed
1638     */
1639    public int removeTrack(long id) {
1640        int numremoved = 0;
1641        synchronized (this) {
1642            for (int i = 0; i < mPlayListLen; i++) {
1643                if (mPlayList[i] == id) {
1644                    numremoved += removeTracksInternal(i, i);
1645                    i--;
1646                }
1647            }
1648        }
1649        if (numremoved > 0) {
1650            notifyChange(QUEUE_CHANGED);
1651        }
1652        return numremoved;
1653    }
1654
1655    public void setShuffleMode(int shufflemode) {
1656        synchronized(this) {
1657            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1658                return;
1659            }
1660            mShuffleMode = shufflemode;
1661            if (mShuffleMode == SHUFFLE_AUTO) {
1662                if (makeAutoShuffleList()) {
1663                    mPlayListLen = 0;
1664                    doAutoShuffleUpdate();
1665                    mPlayPos = 0;
1666                    openCurrentAndNext();
1667                    play();
1668                    notifyChange(META_CHANGED);
1669                    return;
1670                } else {
1671                    // failed to build a list of files to shuffle
1672                    mShuffleMode = SHUFFLE_NONE;
1673                }
1674            }
1675            saveQueue(false);
1676        }
1677    }
1678    public int getShuffleMode() {
1679        return mShuffleMode;
1680    }
1681
1682    public void setRepeatMode(int repeatmode) {
1683        synchronized(this) {
1684            mRepeatMode = repeatmode;
1685            setNextTrack();
1686            saveQueue(false);
1687        }
1688    }
1689    public int getRepeatMode() {
1690        return mRepeatMode;
1691    }
1692
1693    public int getMediaMountedCount() {
1694        return mMediaMountedCount;
1695    }
1696
1697    /**
1698     * Returns the path of the currently playing file, or null if
1699     * no file is currently playing.
1700     */
1701    public String getPath() {
1702        return mFileToPlay;
1703    }
1704
1705    /**
1706     * Returns the rowid of the currently playing file, or -1 if
1707     * no file is currently playing.
1708     */
1709    public long getAudioId() {
1710        synchronized (this) {
1711            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1712                return mPlayList[mPlayPos];
1713            }
1714        }
1715        return -1;
1716    }
1717
1718    /**
1719     * Returns the position in the queue
1720     * @return the position in the queue
1721     */
1722    public int getQueuePosition() {
1723        synchronized(this) {
1724            return mPlayPos;
1725        }
1726    }
1727
1728    /**
1729     * Starts playing the track at the given position in the queue.
1730     * @param pos The position in the queue of the track that will be played.
1731     */
1732    public void setQueuePosition(int pos) {
1733        synchronized(this) {
1734            stop(false);
1735            mPlayPos = pos;
1736            openCurrentAndNext();
1737            play();
1738            notifyChange(META_CHANGED);
1739            if (mShuffleMode == SHUFFLE_AUTO) {
1740                doAutoShuffleUpdate();
1741            }
1742        }
1743    }
1744
1745    public String getArtistName() {
1746        synchronized(this) {
1747            if (mCursor == null) {
1748                return null;
1749            }
1750            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1751        }
1752    }
1753
1754    public long getArtistId() {
1755        synchronized (this) {
1756            if (mCursor == null) {
1757                return -1;
1758            }
1759            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1760        }
1761    }
1762
1763    public String getAlbumName() {
1764        synchronized (this) {
1765            if (mCursor == null) {
1766                return null;
1767            }
1768            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1769        }
1770    }
1771
1772    public long getAlbumId() {
1773        synchronized (this) {
1774            if (mCursor == null) {
1775                return -1;
1776            }
1777            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1778        }
1779    }
1780
1781    public String getTrackName() {
1782        synchronized (this) {
1783            if (mCursor == null) {
1784                return null;
1785            }
1786            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1787        }
1788    }
1789
1790    private boolean isPodcast() {
1791        synchronized (this) {
1792            if (mCursor == null) {
1793                return false;
1794            }
1795            return (mCursor.getInt(PODCASTCOLIDX) > 0);
1796        }
1797    }
1798
1799    private long getBookmark() {
1800        synchronized (this) {
1801            if (mCursor == null) {
1802                return 0;
1803            }
1804            return mCursor.getLong(BOOKMARKCOLIDX);
1805        }
1806    }
1807
1808    /**
1809     * Returns the duration of the file in milliseconds.
1810     * Currently this method returns -1 for the duration of MIDI files.
1811     */
1812    public long duration() {
1813        if (mPlayer.isInitialized()) {
1814            return mPlayer.duration();
1815        }
1816        return -1;
1817    }
1818
1819    /**
1820     * Returns the current playback position in milliseconds
1821     */
1822    public long position() {
1823        if (mPlayer.isInitialized()) {
1824            return mPlayer.position();
1825        }
1826        return -1;
1827    }
1828
1829    /**
1830     * Seeks to the position specified.
1831     *
1832     * @param pos The position to seek to, in milliseconds
1833     */
1834    public long seek(long pos) {
1835        if (mPlayer.isInitialized()) {
1836            if (pos < 0) pos = 0;
1837            if (pos > mPlayer.duration()) pos = mPlayer.duration();
1838            return mPlayer.seek(pos);
1839        }
1840        return -1;
1841    }
1842
1843    /**
1844     * Sets the audio session ID.
1845     *
1846     * @param sessionId: the audio session ID.
1847     */
1848    public void setAudioSessionId(int sessionId) {
1849        synchronized (this) {
1850            mPlayer.setAudioSessionId(sessionId);
1851        }
1852    }
1853
1854    /**
1855     * Returns the audio session ID.
1856     */
1857    public int getAudioSessionId() {
1858        synchronized (this) {
1859            return mPlayer.getAudioSessionId();
1860        }
1861    }
1862
1863    /**
1864     * Provides a unified interface for dealing with midi files and
1865     * other media files.
1866     */
1867    private class MultiPlayer {
1868        private CompatMediaPlayer mCurrentMediaPlayer = new CompatMediaPlayer();
1869        private CompatMediaPlayer mNextMediaPlayer;
1870        private Handler mHandler;
1871        private boolean mIsInitialized = false;
1872
1873        public MultiPlayer() {
1874            mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1875        }
1876
1877        public void setDataSource(String path) {
1878            mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
1879            if (mIsInitialized) {
1880                setNextDataSource(null);
1881            }
1882        }
1883
1884        private boolean setDataSourceImpl(MediaPlayer player, String path) {
1885            try {
1886                player.reset();
1887                player.setOnPreparedListener(null);
1888                if (path.startsWith("content://")) {
1889                    player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1890                } else {
1891                    player.setDataSource(path);
1892                }
1893                player.setAudioStreamType(AudioManager.STREAM_MUSIC);
1894                player.prepare();
1895            } catch (IOException ex) {
1896                // TODO: notify the user why the file couldn't be opened
1897                return false;
1898            } catch (IllegalArgumentException ex) {
1899                // TODO: notify the user why the file couldn't be opened
1900                return false;
1901            }
1902            player.setOnCompletionListener(listener);
1903            player.setOnErrorListener(errorListener);
1904            Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1905            i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1906            i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1907            sendBroadcast(i);
1908            return true;
1909        }
1910
1911        public void setNextDataSource(String path) {
1912            mCurrentMediaPlayer.setNextMediaPlayer(null);
1913            if (mNextMediaPlayer != null) {
1914                mNextMediaPlayer.release();
1915                mNextMediaPlayer = null;
1916            }
1917            if (path == null) {
1918                return;
1919            }
1920            mNextMediaPlayer = new CompatMediaPlayer();
1921            mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1922            mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
1923            if (setDataSourceImpl(mNextMediaPlayer, path)) {
1924                mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
1925            } else {
1926                // failed to open next, we'll transition the old fashioned way,
1927                // which will skip over the faulty file
1928                mNextMediaPlayer.release();
1929                mNextMediaPlayer = null;
1930            }
1931        }
1932
1933        public boolean isInitialized() {
1934            return mIsInitialized;
1935        }
1936
1937        public void start() {
1938            MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
1939            mCurrentMediaPlayer.start();
1940        }
1941
1942        public void stop() {
1943            mCurrentMediaPlayer.reset();
1944            mIsInitialized = false;
1945        }
1946
1947        /**
1948         * You CANNOT use this player anymore after calling release()
1949         */
1950        public void release() {
1951            stop();
1952            mCurrentMediaPlayer.release();
1953        }
1954
1955        public void pause() {
1956            mCurrentMediaPlayer.pause();
1957        }
1958
1959        public void setHandler(Handler handler) {
1960            mHandler = handler;
1961        }
1962
1963        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1964            public void onCompletion(MediaPlayer mp) {
1965                if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
1966                    mCurrentMediaPlayer.release();
1967                    mCurrentMediaPlayer = mNextMediaPlayer;
1968                    mNextMediaPlayer = null;
1969                    mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
1970                } else {
1971                    // Acquire a temporary wakelock, since when we return from
1972                    // this callback the MediaPlayer will release its wakelock
1973                    // and allow the device to go to sleep.
1974                    // This temporary wakelock is released when the RELEASE_WAKELOCK
1975                    // message is processed, but just in case, put a timeout on it.
1976                    mWakeLock.acquire(30000);
1977                    mHandler.sendEmptyMessage(TRACK_ENDED);
1978                    mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1979                }
1980            }
1981        };
1982
1983        MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1984            public boolean onError(MediaPlayer mp, int what, int extra) {
1985                switch (what) {
1986                case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1987                    mIsInitialized = false;
1988                    mCurrentMediaPlayer.release();
1989                    // Creating a new MediaPlayer and settings its wakemode does not
1990                    // require the media service, so it's OK to do this now, while the
1991                    // service is still being restarted
1992                    mCurrentMediaPlayer = new CompatMediaPlayer();
1993                    mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1994                    mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1995                    return true;
1996                default:
1997                    Log.d("MultiPlayer", "Error: " + what + "," + extra);
1998                    break;
1999                }
2000                return false;
2001           }
2002        };
2003
2004        public long duration() {
2005            return mCurrentMediaPlayer.getDuration();
2006        }
2007
2008        public long position() {
2009            return mCurrentMediaPlayer.getCurrentPosition();
2010        }
2011
2012        public long seek(long whereto) {
2013            mCurrentMediaPlayer.seekTo((int) whereto);
2014            return whereto;
2015        }
2016
2017        public void setVolume(float vol) {
2018            mCurrentMediaPlayer.setVolume(vol, vol);
2019        }
2020
2021        public void setAudioSessionId(int sessionId) {
2022            mCurrentMediaPlayer.setAudioSessionId(sessionId);
2023        }
2024
2025        public int getAudioSessionId() {
2026            return mCurrentMediaPlayer.getAudioSessionId();
2027        }
2028    }
2029
2030    static class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
2031
2032        private boolean mCompatMode = true;
2033        private MediaPlayer mNextPlayer;
2034        private OnCompletionListener mCompletion;
2035
2036        public CompatMediaPlayer() {
2037            try {
2038                MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
2039                mCompatMode = false;
2040            } catch (NoSuchMethodException e) {
2041                mCompatMode = true;
2042                super.setOnCompletionListener(this);
2043            }
2044        }
2045
2046        public void setNextMediaPlayer(MediaPlayer next) {
2047            if (mCompatMode) {
2048                mNextPlayer = next;
2049            } else {
2050                super.setNextMediaPlayer(next);
2051            }
2052        }
2053
2054        @Override
2055        public void setOnCompletionListener(OnCompletionListener listener) {
2056            if (mCompatMode) {
2057                mCompletion = listener;
2058            } else {
2059                super.setOnCompletionListener(listener);
2060            }
2061        }
2062
2063        @Override
2064        public void onCompletion(MediaPlayer mp) {
2065            if (mNextPlayer != null) {
2066                // as it turns out, starting a new MediaPlayer on the completion
2067                // of a previous player ends up slightly overlapping the two
2068                // playbacks, so slightly delaying the start of the next player
2069                // gives a better user experience
2070                SystemClock.sleep(50);
2071                mNextPlayer.start();
2072            }
2073            mCompletion.onCompletion(this);
2074        }
2075    }
2076
2077    /*
2078     * By making this a static class with a WeakReference to the Service, we
2079     * ensure that the Service can be GCd even when the system process still
2080     * has a remote reference to the stub.
2081     */
2082    static class ServiceStub extends IMediaPlaybackService.Stub {
2083        WeakReference<MediaPlaybackService> mService;
2084
2085        ServiceStub(MediaPlaybackService service) {
2086            mService = new WeakReference<MediaPlaybackService>(service);
2087        }
2088
2089        public void openFile(String path)
2090        {
2091            mService.get().open(path);
2092        }
2093        public void open(long [] list, int position) {
2094            mService.get().open(list, position);
2095        }
2096        public int getQueuePosition() {
2097            return mService.get().getQueuePosition();
2098        }
2099        public void setQueuePosition(int index) {
2100            mService.get().setQueuePosition(index);
2101        }
2102        public boolean isPlaying() {
2103            return mService.get().isPlaying();
2104        }
2105        public void stop() {
2106            mService.get().stop();
2107        }
2108        public void pause() {
2109            mService.get().pause();
2110        }
2111        public void play() {
2112            mService.get().play();
2113        }
2114        public void prev() {
2115            mService.get().prev();
2116        }
2117        public void next() {
2118            mService.get().gotoNext(true);
2119        }
2120        public String getTrackName() {
2121            return mService.get().getTrackName();
2122        }
2123        public String getAlbumName() {
2124            return mService.get().getAlbumName();
2125        }
2126        public long getAlbumId() {
2127            return mService.get().getAlbumId();
2128        }
2129        public String getArtistName() {
2130            return mService.get().getArtistName();
2131        }
2132        public long getArtistId() {
2133            return mService.get().getArtistId();
2134        }
2135        public void enqueue(long [] list , int action) {
2136            mService.get().enqueue(list, action);
2137        }
2138        public long [] getQueue() {
2139            return mService.get().getQueue();
2140        }
2141        public void moveQueueItem(int from, int to) {
2142            mService.get().moveQueueItem(from, to);
2143        }
2144        public String getPath() {
2145            return mService.get().getPath();
2146        }
2147        public long getAudioId() {
2148            return mService.get().getAudioId();
2149        }
2150        public long position() {
2151            return mService.get().position();
2152        }
2153        public long duration() {
2154            return mService.get().duration();
2155        }
2156        public long seek(long pos) {
2157            return mService.get().seek(pos);
2158        }
2159        public void setShuffleMode(int shufflemode) {
2160            mService.get().setShuffleMode(shufflemode);
2161        }
2162        public int getShuffleMode() {
2163            return mService.get().getShuffleMode();
2164        }
2165        public int removeTracks(int first, int last) {
2166            return mService.get().removeTracks(first, last);
2167        }
2168        public int removeTrack(long id) {
2169            return mService.get().removeTrack(id);
2170        }
2171        public void setRepeatMode(int repeatmode) {
2172            mService.get().setRepeatMode(repeatmode);
2173        }
2174        public int getRepeatMode() {
2175            return mService.get().getRepeatMode();
2176        }
2177        public int getMediaMountedCount() {
2178            return mService.get().getMediaMountedCount();
2179        }
2180        public int getAudioSessionId() {
2181            return mService.get().getAudioSessionId();
2182        }
2183    }
2184
2185    @Override
2186    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
2187        writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
2188        writer.println("Currently loaded:");
2189        writer.println(getArtistName());
2190        writer.println(getAlbumName());
2191        writer.println(getTrackName());
2192        writer.println(getPath());
2193        writer.println("playing: " + mIsSupposedToBePlaying);
2194        writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
2195        writer.println("shuffle mode: " + mShuffleMode);
2196        MusicUtils.debugDump(writer);
2197    }
2198
2199    private final IBinder mBinder = new ServiceStub(this);
2200}
2201