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