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