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