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