MediaPlaybackService.java revision 8d08ec235831d71fdd7f7b6f7757c2bc19528fae
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        ed.commit();
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    }
790
791    /**
792     * Appends a list of tracks to the current playlist.
793     * If nothing is playing currently, playback will be started at
794     * the first track.
795     * If the action is NOW, playback will switch to the first of
796     * the new tracks immediately.
797     * @param list The list of tracks to append.
798     * @param action NOW, NEXT or LAST
799     */
800    public void enqueue(long [] list, int action) {
801        synchronized(this) {
802            if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
803                addToPlayList(list, mPlayPos + 1);
804                notifyChange(QUEUE_CHANGED);
805            } else {
806                // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
807                addToPlayList(list, Integer.MAX_VALUE);
808                notifyChange(QUEUE_CHANGED);
809                if (action == NOW) {
810                    mPlayPos = mPlayListLen - list.length;
811                    openCurrent();
812                    play();
813                    notifyChange(META_CHANGED);
814                    return;
815                }
816            }
817            if (mPlayPos < 0) {
818                mPlayPos = 0;
819                openCurrent();
820                play();
821                notifyChange(META_CHANGED);
822            }
823        }
824    }
825
826    /**
827     * Replaces the current playlist with a new list,
828     * and prepares for starting playback at the specified
829     * position in the list, or a random position if the
830     * specified position is 0.
831     * @param list The new list of tracks.
832     */
833    public void open(long [] list, int position) {
834        synchronized (this) {
835            if (mShuffleMode == SHUFFLE_AUTO) {
836                mShuffleMode = SHUFFLE_NORMAL;
837            }
838            long oldId = getAudioId();
839            int listlength = list.length;
840            boolean newlist = true;
841            if (mPlayListLen == listlength) {
842                // possible fast path: list might be the same
843                newlist = false;
844                for (int i = 0; i < listlength; i++) {
845                    if (list[i] != mPlayList[i]) {
846                        newlist = true;
847                        break;
848                    }
849                }
850            }
851            if (newlist) {
852                addToPlayList(list, -1);
853                notifyChange(QUEUE_CHANGED);
854            }
855            int oldpos = mPlayPos;
856            if (position >= 0) {
857                mPlayPos = position;
858            } else {
859                mPlayPos = mRand.nextInt(mPlayListLen);
860            }
861            mHistory.clear();
862
863            saveBookmarkIfNeeded();
864            openCurrent();
865            if (oldId != getAudioId()) {
866                notifyChange(META_CHANGED);
867            }
868        }
869    }
870
871    /**
872     * Moves the item at index1 to index2.
873     * @param index1
874     * @param index2
875     */
876    public void moveQueueItem(int index1, int index2) {
877        synchronized (this) {
878            if (index1 >= mPlayListLen) {
879                index1 = mPlayListLen - 1;
880            }
881            if (index2 >= mPlayListLen) {
882                index2 = mPlayListLen - 1;
883            }
884            if (index1 < index2) {
885                long tmp = mPlayList[index1];
886                for (int i = index1; i < index2; i++) {
887                    mPlayList[i] = mPlayList[i+1];
888                }
889                mPlayList[index2] = tmp;
890                if (mPlayPos == index1) {
891                    mPlayPos = index2;
892                } else if (mPlayPos >= index1 && mPlayPos <= index2) {
893                        mPlayPos--;
894                }
895            } else if (index2 < index1) {
896                long tmp = mPlayList[index1];
897                for (int i = index1; i > index2; i--) {
898                    mPlayList[i] = mPlayList[i-1];
899                }
900                mPlayList[index2] = tmp;
901                if (mPlayPos == index1) {
902                    mPlayPos = index2;
903                } else if (mPlayPos >= index2 && mPlayPos <= index1) {
904                        mPlayPos++;
905                }
906            }
907            notifyChange(QUEUE_CHANGED);
908        }
909    }
910
911    /**
912     * Returns the current play list
913     * @return An array of integers containing the IDs of the tracks in the play list
914     */
915    public long [] getQueue() {
916        synchronized (this) {
917            int len = mPlayListLen;
918            long [] list = new long[len];
919            for (int i = 0; i < len; i++) {
920                list[i] = mPlayList[i];
921            }
922            return list;
923        }
924    }
925
926    private void openCurrent() {
927        synchronized (this) {
928            if (mCursor != null) {
929                mCursor.close();
930                mCursor = null;
931            }
932
933            if (mPlayListLen == 0) {
934                return;
935            }
936            stop(false);
937
938            String id = String.valueOf(mPlayList[mPlayPos]);
939
940            mCursor = getContentResolver().query(
941                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
942                    mCursorCols, "_id=" + id , null, null);
943            if (mCursor != null) {
944                mCursor.moveToFirst();
945                open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
946                // go to bookmark if needed
947                if (isPodcast()) {
948                    long bookmark = getBookmark();
949                    // Start playing a little bit before the bookmark,
950                    // so it's easier to get back in to the narrative.
951                    seek(bookmark - 5000);
952                }
953            }
954        }
955    }
956
957    /**
958     * Opens the specified file and readies it for playback.
959     *
960     * @param path The full path of the file to be opened.
961     */
962    public void open(String path) {
963        synchronized (this) {
964            if (path == null) {
965                return;
966            }
967
968            // if mCursor is null, try to associate path with a database cursor
969            if (mCursor == null) {
970
971                ContentResolver resolver = getContentResolver();
972                Uri uri;
973                String where;
974                String selectionArgs[];
975                if (path.startsWith("content://media/")) {
976                    uri = Uri.parse(path);
977                    where = null;
978                    selectionArgs = null;
979                } else {
980                   uri = MediaStore.Audio.Media.getContentUriForPath(path);
981                   where = MediaStore.Audio.Media.DATA + "=?";
982                   selectionArgs = new String[] { path };
983                }
984
985                try {
986                    mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
987                    if  (mCursor != null) {
988                        if (mCursor.getCount() == 0) {
989                            mCursor.close();
990                            mCursor = null;
991                        } else {
992                            mCursor.moveToNext();
993                            ensurePlayListCapacity(1);
994                            mPlayListLen = 1;
995                            mPlayList[0] = mCursor.getLong(IDCOLIDX);
996                            mPlayPos = 0;
997                        }
998                    }
999                } catch (UnsupportedOperationException ex) {
1000                }
1001            }
1002            mFileToPlay = path;
1003            mPlayer.setDataSource(mFileToPlay);
1004            if (! mPlayer.isInitialized()) {
1005                stop(true);
1006                if (mOpenFailedCounter++ < 10 &&  mPlayListLen > 1) {
1007                    // beware: this ends up being recursive because next() calls open() again.
1008                    next(false);
1009                }
1010                if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1011                    // need to make sure we only shows this once
1012                    mOpenFailedCounter = 0;
1013                    if (!mQuietMode) {
1014                        Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1015                    }
1016                    Log.d(LOGTAG, "Failed to open file for playback");
1017                }
1018            } else {
1019                mOpenFailedCounter = 0;
1020            }
1021        }
1022    }
1023
1024    /**
1025     * Starts playback of a previously opened file.
1026     */
1027    public void play() {
1028        mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1029                AudioManager.AUDIOFOCUS_GAIN);
1030        mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1031                MediaButtonIntentReceiver.class.getName()));
1032
1033        if (mPlayer.isInitialized()) {
1034            // if we are at the end of the song, go to the next song first
1035            long duration = mPlayer.duration();
1036            if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1037                mPlayer.position() >= duration - 2000) {
1038                next(true);
1039            }
1040
1041            mPlayer.start();
1042
1043            RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1044            views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1045            if (getAudioId() < 0) {
1046                // streaming
1047                views.setTextViewText(R.id.trackname, getPath());
1048                views.setTextViewText(R.id.artistalbum, null);
1049            } else {
1050                String artist = getArtistName();
1051                views.setTextViewText(R.id.trackname, getTrackName());
1052                if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1053                    artist = getString(R.string.unknown_artist_name);
1054                }
1055                String album = getAlbumName();
1056                if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1057                    album = getString(R.string.unknown_album_name);
1058                }
1059
1060                views.setTextViewText(R.id.artistalbum,
1061                        getString(R.string.notification_artist_album, artist, album)
1062                        );
1063            }
1064
1065            Notification status = new Notification();
1066            status.contentView = views;
1067            status.flags |= Notification.FLAG_ONGOING_EVENT;
1068            status.icon = R.drawable.stat_notify_musicplayer;
1069            status.contentIntent = PendingIntent.getActivity(this, 0,
1070                    new Intent("com.android.music.PLAYBACK_VIEWER")
1071                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1072            startForeground(PLAYBACKSERVICE_STATUS, status);
1073            if (!mIsSupposedToBePlaying) {
1074                mIsSupposedToBePlaying = true;
1075                notifyChange(PLAYSTATE_CHANGED);
1076            }
1077
1078        } else if (mPlayListLen <= 0) {
1079            // This is mostly so that if you press 'play' on a bluetooth headset
1080            // without every having played anything before, it will still play
1081            // something.
1082            setShuffleMode(SHUFFLE_AUTO);
1083        }
1084    }
1085
1086    private void stop(boolean remove_status_icon) {
1087        if (mPlayer.isInitialized()) {
1088            mPlayer.stop();
1089        }
1090        mFileToPlay = null;
1091        if (mCursor != null) {
1092            mCursor.close();
1093            mCursor = null;
1094        }
1095        if (remove_status_icon) {
1096            gotoIdleState();
1097        } else {
1098            stopForeground(false);
1099        }
1100        if (remove_status_icon) {
1101            mIsSupposedToBePlaying = false;
1102        }
1103    }
1104
1105    /**
1106     * Stops playback.
1107     */
1108    public void stop() {
1109        stop(true);
1110    }
1111
1112    /**
1113     * Pauses playback (call play() to resume)
1114     */
1115    public void pause() {
1116        synchronized(this) {
1117            if (isPlaying()) {
1118                mPlayer.pause();
1119                gotoIdleState();
1120                mIsSupposedToBePlaying = false;
1121                notifyChange(PLAYSTATE_CHANGED);
1122                saveBookmarkIfNeeded();
1123            }
1124        }
1125    }
1126
1127    /** Returns whether something is currently playing
1128     *
1129     * @return true if something is playing (or will be playing shortly, in case
1130     * we're currently transitioning between tracks), false if not.
1131     */
1132    public boolean isPlaying() {
1133        return mIsSupposedToBePlaying;
1134    }
1135
1136    /*
1137      Desired behavior for prev/next/shuffle:
1138
1139      - NEXT will move to the next track in the list when not shuffling, and to
1140        a track randomly picked from the not-yet-played tracks when shuffling.
1141        If all tracks have already been played, pick from the full set, but
1142        avoid picking the previously played track if possible.
1143      - when shuffling, PREV will go to the previously played track. Hitting PREV
1144        again will go to the track played before that, etc. When the start of the
1145        history has been reached, PREV is a no-op.
1146        When not shuffling, PREV will go to the sequentially previous track (the
1147        difference with the shuffle-case is mainly that when not shuffling, the
1148        user can back up to tracks that are not in the history).
1149
1150        Example:
1151        When playing an album with 10 tracks from the start, and enabling shuffle
1152        while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1153        the final play order might be 1-2-3-4-5-8-10-6-9-7.
1154        When hitting 'prev' 8 times while playing track 7 in this example, the
1155        user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1156        a random track will be picked again. If at any time user disables shuffling
1157        the next/previous track will be picked in sequential order again.
1158     */
1159
1160    public void prev() {
1161        synchronized (this) {
1162            if (mShuffleMode == SHUFFLE_NORMAL) {
1163                // go to previously-played track and remove it from the history
1164                int histsize = mHistory.size();
1165                if (histsize == 0) {
1166                    // prev is a no-op
1167                    return;
1168                }
1169                Integer pos = mHistory.remove(histsize - 1);
1170                mPlayPos = pos.intValue();
1171            } else {
1172                if (mPlayPos > 0) {
1173                    mPlayPos--;
1174                } else {
1175                    mPlayPos = mPlayListLen - 1;
1176                }
1177            }
1178            saveBookmarkIfNeeded();
1179            stop(false);
1180            openCurrent();
1181            play();
1182            notifyChange(META_CHANGED);
1183        }
1184    }
1185
1186    public void next(boolean force) {
1187        synchronized (this) {
1188            if (mPlayListLen <= 0) {
1189                Log.d(LOGTAG, "No play queue");
1190                return;
1191            }
1192
1193            // Store the current file in the history, but keep the history at a
1194            // reasonable size
1195            if (mPlayPos >= 0) {
1196                mHistory.add(Integer.valueOf(mPlayPos));
1197            }
1198            if (mHistory.size() > MAX_HISTORY_SIZE) {
1199                mHistory.removeElementAt(0);
1200            }
1201
1202            if (mShuffleMode == SHUFFLE_NORMAL) {
1203                // Pick random next track from the not-yet-played ones
1204                // TODO: make it work right after adding/removing items in the queue.
1205
1206                int numTracks = mPlayListLen;
1207                int[] tracks = new int[numTracks];
1208                for (int i=0;i < numTracks; i++) {
1209                    tracks[i] = i;
1210                }
1211
1212                int numHistory = mHistory.size();
1213                int numUnplayed = numTracks;
1214                for (int i=0;i < numHistory; i++) {
1215                    int idx = mHistory.get(i).intValue();
1216                    if (idx < numTracks && tracks[idx] >= 0) {
1217                        numUnplayed--;
1218                        tracks[idx] = -1;
1219                    }
1220                }
1221
1222                // 'numUnplayed' now indicates how many tracks have not yet
1223                // been played, and 'tracks' contains the indices of those
1224                // tracks.
1225                if (numUnplayed <=0) {
1226                    // everything's already been played
1227                    if (mRepeatMode == REPEAT_ALL || force) {
1228                        //pick from full set
1229                        numUnplayed = numTracks;
1230                        for (int i=0;i < numTracks; i++) {
1231                            tracks[i] = i;
1232                        }
1233                    } else {
1234                        // all done
1235                        gotoIdleState();
1236                        if (mIsSupposedToBePlaying) {
1237                            mIsSupposedToBePlaying = false;
1238                            notifyChange(PLAYSTATE_CHANGED);
1239                        }
1240                        return;
1241                    }
1242                }
1243                int skip = mRand.nextInt(numUnplayed);
1244                int cnt = -1;
1245                while (true) {
1246                    while (tracks[++cnt] < 0)
1247                        ;
1248                    skip--;
1249                    if (skip < 0) {
1250                        break;
1251                    }
1252                }
1253                mPlayPos = cnt;
1254            } else if (mShuffleMode == SHUFFLE_AUTO) {
1255                doAutoShuffleUpdate();
1256                mPlayPos++;
1257            } else {
1258                if (mPlayPos >= mPlayListLen - 1) {
1259                    // we're at the end of the list
1260                    if (mRepeatMode == REPEAT_NONE && !force) {
1261                        // all done
1262                        gotoIdleState();
1263                        notifyChange(PLAYBACK_COMPLETE);
1264                        mIsSupposedToBePlaying = false;
1265                        return;
1266                    } else if (mRepeatMode == REPEAT_ALL || force) {
1267                        mPlayPos = 0;
1268                    }
1269                } else {
1270                    mPlayPos++;
1271                }
1272            }
1273            saveBookmarkIfNeeded();
1274            stop(false);
1275            openCurrent();
1276            play();
1277            notifyChange(META_CHANGED);
1278        }
1279    }
1280
1281    private void gotoIdleState() {
1282        mDelayedStopHandler.removeCallbacksAndMessages(null);
1283        Message msg = mDelayedStopHandler.obtainMessage();
1284        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1285        stopForeground(true);
1286    }
1287
1288    private void saveBookmarkIfNeeded() {
1289        try {
1290            if (isPodcast()) {
1291                long pos = position();
1292                long bookmark = getBookmark();
1293                long duration = duration();
1294                if ((pos < bookmark && (pos + 10000) > bookmark) ||
1295                        (pos > bookmark && (pos - 10000) < bookmark)) {
1296                    // The existing bookmark is close to the current
1297                    // position, so don't update it.
1298                    return;
1299                }
1300                if (pos < 15000 || (pos + 10000) > duration) {
1301                    // if we're near the start or end, clear the bookmark
1302                    pos = 0;
1303                }
1304
1305                // write 'pos' to the bookmark field
1306                ContentValues values = new ContentValues();
1307                values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1308                Uri uri = ContentUris.withAppendedId(
1309                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1310                getContentResolver().update(uri, values, null, null);
1311            }
1312        } catch (SQLiteException ex) {
1313        }
1314    }
1315
1316    // Make sure there are at least 5 items after the currently playing item
1317    // and no more than 10 items before.
1318    private void doAutoShuffleUpdate() {
1319        boolean notify = false;
1320        // remove old entries
1321        if (mPlayPos > 10) {
1322            removeTracks(0, mPlayPos - 9);
1323            notify = true;
1324        }
1325        // add new entries if needed
1326        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1327        for (int i = 0; i < to_add; i++) {
1328            // pick something at random from the list
1329            int idx = mRand.nextInt(mAutoShuffleList.length);
1330            long which = mAutoShuffleList[idx];
1331            ensurePlayListCapacity(mPlayListLen + 1);
1332            mPlayList[mPlayListLen++] = which;
1333            notify = true;
1334        }
1335        if (notify) {
1336            notifyChange(QUEUE_CHANGED);
1337        }
1338    }
1339
1340    // A simple variation of Random that makes sure that the
1341    // value it returns is not equal to the value it returned
1342    // previously, unless the interval is 1.
1343    private static class Shuffler {
1344        private int mPrevious;
1345        private Random mRandom = new Random();
1346        public int nextInt(int interval) {
1347            int ret;
1348            do {
1349                ret = mRandom.nextInt(interval);
1350            } while (ret == mPrevious && interval > 1);
1351            mPrevious = ret;
1352            return ret;
1353        }
1354    };
1355
1356    private boolean makeAutoShuffleList() {
1357        ContentResolver res = getContentResolver();
1358        Cursor c = null;
1359        try {
1360            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1361                    new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1362                    null, null);
1363            if (c == null || c.getCount() == 0) {
1364                return false;
1365            }
1366            int len = c.getCount();
1367            long [] list = new long[len];
1368            for (int i = 0; i < len; i++) {
1369                c.moveToNext();
1370                list[i] = c.getLong(0);
1371            }
1372            mAutoShuffleList = list;
1373            return true;
1374        } catch (RuntimeException ex) {
1375        } finally {
1376            if (c != null) {
1377                c.close();
1378            }
1379        }
1380        return false;
1381    }
1382
1383    /**
1384     * Removes the range of tracks specified from the play list. If a file within the range is
1385     * the file currently being played, playback will move to the next file after the
1386     * range.
1387     * @param first The first file to be removed
1388     * @param last The last file to be removed
1389     * @return the number of tracks deleted
1390     */
1391    public int removeTracks(int first, int last) {
1392        int numremoved = removeTracksInternal(first, last);
1393        if (numremoved > 0) {
1394            notifyChange(QUEUE_CHANGED);
1395        }
1396        return numremoved;
1397    }
1398
1399    private int removeTracksInternal(int first, int last) {
1400        synchronized (this) {
1401            if (last < first) return 0;
1402            if (first < 0) first = 0;
1403            if (last >= mPlayListLen) last = mPlayListLen - 1;
1404
1405            boolean gotonext = false;
1406            if (first <= mPlayPos && mPlayPos <= last) {
1407                mPlayPos = first;
1408                gotonext = true;
1409            } else if (mPlayPos > last) {
1410                mPlayPos -= (last - first + 1);
1411            }
1412            int num = mPlayListLen - last - 1;
1413            for (int i = 0; i < num; i++) {
1414                mPlayList[first + i] = mPlayList[last + 1 + i];
1415            }
1416            mPlayListLen -= last - first + 1;
1417
1418            if (gotonext) {
1419                if (mPlayListLen == 0) {
1420                    stop(true);
1421                    mPlayPos = -1;
1422                } else {
1423                    if (mPlayPos >= mPlayListLen) {
1424                        mPlayPos = 0;
1425                    }
1426                    boolean wasPlaying = isPlaying();
1427                    stop(false);
1428                    openCurrent();
1429                    if (wasPlaying) {
1430                        play();
1431                    }
1432                }
1433            }
1434            return last - first + 1;
1435        }
1436    }
1437
1438    /**
1439     * Removes all instances of the track with the given id
1440     * from the playlist.
1441     * @param id The id to be removed
1442     * @return how many instances of the track were removed
1443     */
1444    public int removeTrack(long id) {
1445        int numremoved = 0;
1446        synchronized (this) {
1447            for (int i = 0; i < mPlayListLen; i++) {
1448                if (mPlayList[i] == id) {
1449                    numremoved += removeTracksInternal(i, i);
1450                    i--;
1451                }
1452            }
1453        }
1454        if (numremoved > 0) {
1455            notifyChange(QUEUE_CHANGED);
1456        }
1457        return numremoved;
1458    }
1459
1460    public void setShuffleMode(int shufflemode) {
1461        synchronized(this) {
1462            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1463                return;
1464            }
1465            mShuffleMode = shufflemode;
1466            if (mShuffleMode == SHUFFLE_AUTO) {
1467                if (makeAutoShuffleList()) {
1468                    mPlayListLen = 0;
1469                    doAutoShuffleUpdate();
1470                    mPlayPos = 0;
1471                    openCurrent();
1472                    play();
1473                    notifyChange(META_CHANGED);
1474                    return;
1475                } else {
1476                    // failed to build a list of files to shuffle
1477                    mShuffleMode = SHUFFLE_NONE;
1478                }
1479            }
1480            saveQueue(false);
1481        }
1482    }
1483    public int getShuffleMode() {
1484        return mShuffleMode;
1485    }
1486
1487    public void setRepeatMode(int repeatmode) {
1488        synchronized(this) {
1489            mRepeatMode = repeatmode;
1490            saveQueue(false);
1491        }
1492    }
1493    public int getRepeatMode() {
1494        return mRepeatMode;
1495    }
1496
1497    public int getMediaMountedCount() {
1498        return mMediaMountedCount;
1499    }
1500
1501    /**
1502     * Returns the path of the currently playing file, or null if
1503     * no file is currently playing.
1504     */
1505    public String getPath() {
1506        return mFileToPlay;
1507    }
1508
1509    /**
1510     * Returns the rowid of the currently playing file, or -1 if
1511     * no file is currently playing.
1512     */
1513    public long getAudioId() {
1514        synchronized (this) {
1515            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1516                return mPlayList[mPlayPos];
1517            }
1518        }
1519        return -1;
1520    }
1521
1522    /**
1523     * Returns the position in the queue
1524     * @return the position in the queue
1525     */
1526    public int getQueuePosition() {
1527        synchronized(this) {
1528            return mPlayPos;
1529        }
1530    }
1531
1532    /**
1533     * Starts playing the track at the given position in the queue.
1534     * @param pos The position in the queue of the track that will be played.
1535     */
1536    public void setQueuePosition(int pos) {
1537        synchronized(this) {
1538            stop(false);
1539            mPlayPos = pos;
1540            openCurrent();
1541            play();
1542            notifyChange(META_CHANGED);
1543            if (mShuffleMode == SHUFFLE_AUTO) {
1544                doAutoShuffleUpdate();
1545            }
1546        }
1547    }
1548
1549    public String getArtistName() {
1550        synchronized(this) {
1551            if (mCursor == null) {
1552                return null;
1553            }
1554            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1555        }
1556    }
1557
1558    public long getArtistId() {
1559        synchronized (this) {
1560            if (mCursor == null) {
1561                return -1;
1562            }
1563            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1564        }
1565    }
1566
1567    public String getAlbumName() {
1568        synchronized (this) {
1569            if (mCursor == null) {
1570                return null;
1571            }
1572            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1573        }
1574    }
1575
1576    public long getAlbumId() {
1577        synchronized (this) {
1578            if (mCursor == null) {
1579                return -1;
1580            }
1581            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1582        }
1583    }
1584
1585    public String getTrackName() {
1586        synchronized (this) {
1587            if (mCursor == null) {
1588                return null;
1589            }
1590            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1591        }
1592    }
1593
1594    private boolean isPodcast() {
1595        synchronized (this) {
1596            if (mCursor == null) {
1597                return false;
1598            }
1599            return (mCursor.getInt(PODCASTCOLIDX) > 0);
1600        }
1601    }
1602
1603    private long getBookmark() {
1604        synchronized (this) {
1605            if (mCursor == null) {
1606                return 0;
1607            }
1608            return mCursor.getLong(BOOKMARKCOLIDX);
1609        }
1610    }
1611
1612    /**
1613     * Returns the duration of the file in milliseconds.
1614     * Currently this method returns -1 for the duration of MIDI files.
1615     */
1616    public long duration() {
1617        if (mPlayer.isInitialized()) {
1618            return mPlayer.duration();
1619        }
1620        return -1;
1621    }
1622
1623    /**
1624     * Returns the current playback position in milliseconds
1625     */
1626    public long position() {
1627        if (mPlayer.isInitialized()) {
1628            return mPlayer.position();
1629        }
1630        return -1;
1631    }
1632
1633    /**
1634     * Seeks to the position specified.
1635     *
1636     * @param pos The position to seek to, in milliseconds
1637     */
1638    public long seek(long pos) {
1639        if (mPlayer.isInitialized()) {
1640            if (pos < 0) pos = 0;
1641            if (pos > mPlayer.duration()) pos = mPlayer.duration();
1642            return mPlayer.seek(pos);
1643        }
1644        return -1;
1645    }
1646
1647    /**
1648     * Provides a unified interface for dealing with midi files and
1649     * other media files.
1650     */
1651    private class MultiPlayer {
1652        private MediaPlayer mMediaPlayer = new MediaPlayer();
1653        private Handler mHandler;
1654        private boolean mIsInitialized = false;
1655
1656        public MultiPlayer() {
1657            mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1658        }
1659
1660        public void setDataSourceAsync(String path) {
1661            try {
1662                mMediaPlayer.reset();
1663                mMediaPlayer.setDataSource(path);
1664                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1665                mMediaPlayer.prepareAsync();
1666            } catch (IOException ex) {
1667                // TODO: notify the user why the file couldn't be opened
1668                mIsInitialized = false;
1669                return;
1670            } catch (IllegalArgumentException ex) {
1671                // TODO: notify the user why the file couldn't be opened
1672                mIsInitialized = false;
1673                return;
1674            }
1675            mMediaPlayer.setOnCompletionListener(listener);
1676            mMediaPlayer.setOnErrorListener(errorListener);
1677
1678            mIsInitialized = true;
1679        }
1680
1681        public void setDataSource(String path) {
1682            try {
1683                mMediaPlayer.reset();
1684                mMediaPlayer.setOnPreparedListener(null);
1685                if (path.startsWith("content://")) {
1686                    mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1687                } else {
1688                    mMediaPlayer.setDataSource(path);
1689                }
1690                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1691                mMediaPlayer.prepare();
1692            } catch (IOException ex) {
1693                // TODO: notify the user why the file couldn't be opened
1694                mIsInitialized = false;
1695                return;
1696            } catch (IllegalArgumentException ex) {
1697                // TODO: notify the user why the file couldn't be opened
1698                mIsInitialized = false;
1699                return;
1700            }
1701            mMediaPlayer.setOnCompletionListener(listener);
1702            mMediaPlayer.setOnErrorListener(errorListener);
1703
1704            mIsInitialized = true;
1705        }
1706
1707        public boolean isInitialized() {
1708            return mIsInitialized;
1709        }
1710
1711        public void start() {
1712            MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
1713            mMediaPlayer.start();
1714        }
1715
1716        public void stop() {
1717            mMediaPlayer.reset();
1718            mIsInitialized = false;
1719        }
1720
1721        /**
1722         * You CANNOT use this player anymore after calling release()
1723         */
1724        public void release() {
1725            stop();
1726            mMediaPlayer.release();
1727        }
1728
1729        public void pause() {
1730            mMediaPlayer.pause();
1731        }
1732
1733        public void setHandler(Handler handler) {
1734            mHandler = handler;
1735        }
1736
1737        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1738            public void onCompletion(MediaPlayer mp) {
1739                // Acquire a temporary wakelock, since when we return from
1740                // this callback the MediaPlayer will release its wakelock
1741                // and allow the device to go to sleep.
1742                // This temporary wakelock is released when the RELEASE_WAKELOCK
1743                // message is processed, but just in case, put a timeout on it.
1744                mWakeLock.acquire(30000);
1745                mHandler.sendEmptyMessage(TRACK_ENDED);
1746                mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1747            }
1748        };
1749
1750        MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1751            public boolean onError(MediaPlayer mp, int what, int extra) {
1752                switch (what) {
1753                case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1754                    mIsInitialized = false;
1755                    mMediaPlayer.release();
1756                    // Creating a new MediaPlayer and settings its wakemode does not
1757                    // require the media service, so it's OK to do this now, while the
1758                    // service is still being restarted
1759                    mMediaPlayer = new MediaPlayer();
1760                    mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1761                    mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1762                    return true;
1763                default:
1764                    Log.d("MultiPlayer", "Error: " + what + "," + extra);
1765                    break;
1766                }
1767                return false;
1768           }
1769        };
1770
1771        public long duration() {
1772            return mMediaPlayer.getDuration();
1773        }
1774
1775        public long position() {
1776            return mMediaPlayer.getCurrentPosition();
1777        }
1778
1779        public long seek(long whereto) {
1780            mMediaPlayer.seekTo((int) whereto);
1781            return whereto;
1782        }
1783
1784        public void setVolume(float vol) {
1785            mMediaPlayer.setVolume(vol, vol);
1786        }
1787    }
1788
1789    /*
1790     * By making this a static class with a WeakReference to the Service, we
1791     * ensure that the Service can be GCd even when the system process still
1792     * has a remote reference to the stub.
1793     */
1794    static class ServiceStub extends IMediaPlaybackService.Stub {
1795        WeakReference<MediaPlaybackService> mService;
1796
1797        ServiceStub(MediaPlaybackService service) {
1798            mService = new WeakReference<MediaPlaybackService>(service);
1799        }
1800
1801        public void openFile(String path)
1802        {
1803            mService.get().open(path);
1804        }
1805        public void open(long [] list, int position) {
1806            mService.get().open(list, position);
1807        }
1808        public int getQueuePosition() {
1809            return mService.get().getQueuePosition();
1810        }
1811        public void setQueuePosition(int index) {
1812            mService.get().setQueuePosition(index);
1813        }
1814        public boolean isPlaying() {
1815            return mService.get().isPlaying();
1816        }
1817        public void stop() {
1818            mService.get().stop();
1819        }
1820        public void pause() {
1821            mService.get().pause();
1822        }
1823        public void play() {
1824            mService.get().play();
1825        }
1826        public void prev() {
1827            mService.get().prev();
1828        }
1829        public void next() {
1830            mService.get().next(true);
1831        }
1832        public String getTrackName() {
1833            return mService.get().getTrackName();
1834        }
1835        public String getAlbumName() {
1836            return mService.get().getAlbumName();
1837        }
1838        public long getAlbumId() {
1839            return mService.get().getAlbumId();
1840        }
1841        public String getArtistName() {
1842            return mService.get().getArtistName();
1843        }
1844        public long getArtistId() {
1845            return mService.get().getArtistId();
1846        }
1847        public void enqueue(long [] list , int action) {
1848            mService.get().enqueue(list, action);
1849        }
1850        public long [] getQueue() {
1851            return mService.get().getQueue();
1852        }
1853        public void moveQueueItem(int from, int to) {
1854            mService.get().moveQueueItem(from, to);
1855        }
1856        public String getPath() {
1857            return mService.get().getPath();
1858        }
1859        public long getAudioId() {
1860            return mService.get().getAudioId();
1861        }
1862        public long position() {
1863            return mService.get().position();
1864        }
1865        public long duration() {
1866            return mService.get().duration();
1867        }
1868        public long seek(long pos) {
1869            return mService.get().seek(pos);
1870        }
1871        public void setShuffleMode(int shufflemode) {
1872            mService.get().setShuffleMode(shufflemode);
1873        }
1874        public int getShuffleMode() {
1875            return mService.get().getShuffleMode();
1876        }
1877        public int removeTracks(int first, int last) {
1878            return mService.get().removeTracks(first, last);
1879        }
1880        public int removeTrack(long id) {
1881            return mService.get().removeTrack(id);
1882        }
1883        public void setRepeatMode(int repeatmode) {
1884            mService.get().setRepeatMode(repeatmode);
1885        }
1886        public int getRepeatMode() {
1887            return mService.get().getRepeatMode();
1888        }
1889        public int getMediaMountedCount() {
1890            return mService.get().getMediaMountedCount();
1891        }
1892
1893    }
1894
1895    @Override
1896    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1897        writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
1898        writer.println("Currently loaded:");
1899        writer.println(getArtistName());
1900        writer.println(getAlbumName());
1901        writer.println(getTrackName());
1902        writer.println(getPath());
1903        writer.println("playing: " + mIsSupposedToBePlaying);
1904        writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
1905        writer.println("shuffle mode: " + mShuffleMode);
1906        MusicUtils.debugDump(writer);
1907    }
1908
1909    private final IBinder mBinder = new ServiceStub(this);
1910}
1911