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