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