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