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