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