MediaPlaybackService.java revision b63b5d1b6cae1cc9e28ba63b316fc15da5e16939
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"), 0);
1076            startForeground(PLAYBACKSERVICE_STATUS, status);
1077            if (!mIsSupposedToBePlaying) {
1078                mIsSupposedToBePlaying = true;
1079                notifyChange(PLAYSTATE_CHANGED);
1080            }
1081
1082        } else if (mPlayListLen <= 0) {
1083            // This is mostly so that if you press 'play' on a bluetooth headset
1084            // without every having played anything before, it will still play
1085            // something.
1086            setShuffleMode(SHUFFLE_AUTO);
1087        }
1088    }
1089
1090    private void stop(boolean remove_status_icon) {
1091        if (mPlayer.isInitialized()) {
1092            mPlayer.stop();
1093        }
1094        mFileToPlay = null;
1095        if (mCursor != null) {
1096            mCursor.close();
1097            mCursor = null;
1098        }
1099        if (remove_status_icon) {
1100            gotoIdleState();
1101        } else {
1102            stopForeground(false);
1103        }
1104        if (remove_status_icon) {
1105            mIsSupposedToBePlaying = false;
1106        }
1107    }
1108
1109    /**
1110     * Stops playback.
1111     */
1112    public void stop() {
1113        stop(true);
1114    }
1115
1116    /**
1117     * Pauses playback (call play() to resume)
1118     */
1119    public void pause() {
1120        synchronized(this) {
1121            if (isPlaying()) {
1122                mPlayer.pause();
1123                gotoIdleState();
1124                mIsSupposedToBePlaying = false;
1125                notifyChange(PLAYSTATE_CHANGED);
1126                saveBookmarkIfNeeded();
1127            }
1128        }
1129    }
1130
1131    /** Returns whether something is currently playing
1132     *
1133     * @return true if something is playing (or will be playing shortly, in case
1134     * we're currently transitioning between tracks), false if not.
1135     */
1136    public boolean isPlaying() {
1137        return mIsSupposedToBePlaying;
1138    }
1139
1140    /*
1141      Desired behavior for prev/next/shuffle:
1142
1143      - NEXT will move to the next track in the list when not shuffling, and to
1144        a track randomly picked from the not-yet-played tracks when shuffling.
1145        If all tracks have already been played, pick from the full set, but
1146        avoid picking the previously played track if possible.
1147      - when shuffling, PREV will go to the previously played track. Hitting PREV
1148        again will go to the track played before that, etc. When the start of the
1149        history has been reached, PREV is a no-op.
1150        When not shuffling, PREV will go to the sequentially previous track (the
1151        difference with the shuffle-case is mainly that when not shuffling, the
1152        user can back up to tracks that are not in the history).
1153
1154        Example:
1155        When playing an album with 10 tracks from the start, and enabling shuffle
1156        while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1157        the final play order might be 1-2-3-4-5-8-10-6-9-7.
1158        When hitting 'prev' 8 times while playing track 7 in this example, the
1159        user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1160        a random track will be picked again. If at any time user disables shuffling
1161        the next/previous track will be picked in sequential order again.
1162     */
1163
1164    public void prev() {
1165        synchronized (this) {
1166            if (mOneShot) {
1167                // we were playing a specific file not part of a playlist, so there is no 'previous'
1168                seek(0);
1169                play();
1170                return;
1171            }
1172            if (mShuffleMode == SHUFFLE_NORMAL) {
1173                // go to previously-played track and remove it from the history
1174                int histsize = mHistory.size();
1175                if (histsize == 0) {
1176                    // prev is a no-op
1177                    return;
1178                }
1179                Integer pos = mHistory.remove(histsize - 1);
1180                mPlayPos = pos.intValue();
1181            } else {
1182                if (mPlayPos > 0) {
1183                    mPlayPos--;
1184                } else {
1185                    mPlayPos = mPlayListLen - 1;
1186                }
1187            }
1188            saveBookmarkIfNeeded();
1189            stop(false);
1190            openCurrent();
1191            play();
1192            notifyChange(META_CHANGED);
1193        }
1194    }
1195
1196    public void next(boolean force) {
1197        synchronized (this) {
1198            if (mOneShot) {
1199                // we were playing a specific file not part of a playlist, so there is no 'next'
1200                seek(0);
1201                play();
1202                return;
1203            }
1204
1205            if (mPlayListLen <= 0) {
1206                Log.d(LOGTAG, "No play queue");
1207                return;
1208            }
1209
1210            // Store the current file in the history, but keep the history at a
1211            // reasonable size
1212            if (mPlayPos >= 0) {
1213                mHistory.add(Integer.valueOf(mPlayPos));
1214            }
1215            if (mHistory.size() > MAX_HISTORY_SIZE) {
1216                mHistory.removeElementAt(0);
1217            }
1218
1219            if (mShuffleMode == SHUFFLE_NORMAL) {
1220                // Pick random next track from the not-yet-played ones
1221                // TODO: make it work right after adding/removing items in the queue.
1222
1223                int numTracks = mPlayListLen;
1224                int[] tracks = new int[numTracks];
1225                for (int i=0;i < numTracks; i++) {
1226                    tracks[i] = i;
1227                }
1228
1229                int numHistory = mHistory.size();
1230                int numUnplayed = numTracks;
1231                for (int i=0;i < numHistory; i++) {
1232                    int idx = mHistory.get(i).intValue();
1233                    if (idx < numTracks && tracks[idx] >= 0) {
1234                        numUnplayed--;
1235                        tracks[idx] = -1;
1236                    }
1237                }
1238
1239                // 'numUnplayed' now indicates how many tracks have not yet
1240                // been played, and 'tracks' contains the indices of those
1241                // tracks.
1242                if (numUnplayed <=0) {
1243                    // everything's already been played
1244                    if (mRepeatMode == REPEAT_ALL || force) {
1245                        //pick from full set
1246                        numUnplayed = numTracks;
1247                        for (int i=0;i < numTracks; i++) {
1248                            tracks[i] = i;
1249                        }
1250                    } else {
1251                        // all done
1252                        gotoIdleState();
1253                        return;
1254                    }
1255                }
1256                int skip = mRand.nextInt(numUnplayed);
1257                int cnt = -1;
1258                while (true) {
1259                    while (tracks[++cnt] < 0)
1260                        ;
1261                    skip--;
1262                    if (skip < 0) {
1263                        break;
1264                    }
1265                }
1266                mPlayPos = cnt;
1267            } else if (mShuffleMode == SHUFFLE_AUTO) {
1268                doAutoShuffleUpdate();
1269                mPlayPos++;
1270            } else {
1271                if (mPlayPos >= mPlayListLen - 1) {
1272                    // we're at the end of the list
1273                    if (mRepeatMode == REPEAT_NONE && !force) {
1274                        // all done
1275                        gotoIdleState();
1276                        notifyChange(PLAYBACK_COMPLETE);
1277                        mIsSupposedToBePlaying = false;
1278                        return;
1279                    } else if (mRepeatMode == REPEAT_ALL || force) {
1280                        mPlayPos = 0;
1281                    }
1282                } else {
1283                    mPlayPos++;
1284                }
1285            }
1286            saveBookmarkIfNeeded();
1287            stop(false);
1288            openCurrent();
1289            play();
1290            notifyChange(META_CHANGED);
1291        }
1292    }
1293
1294    private void gotoIdleState() {
1295        mDelayedStopHandler.removeCallbacksAndMessages(null);
1296        Message msg = mDelayedStopHandler.obtainMessage();
1297        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1298        stopForeground(true);
1299    }
1300
1301    private void saveBookmarkIfNeeded() {
1302        try {
1303            if (isPodcast()) {
1304                long pos = position();
1305                long bookmark = getBookmark();
1306                long duration = duration();
1307                if ((pos < bookmark && (pos + 10000) > bookmark) ||
1308                        (pos > bookmark && (pos - 10000) < bookmark)) {
1309                    // The existing bookmark is close to the current
1310                    // position, so don't update it.
1311                    return;
1312                }
1313                if (pos < 15000 || (pos + 10000) > duration) {
1314                    // if we're near the start or end, clear the bookmark
1315                    pos = 0;
1316                }
1317
1318                // write 'pos' to the bookmark field
1319                ContentValues values = new ContentValues();
1320                values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1321                Uri uri = ContentUris.withAppendedId(
1322                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1323                getContentResolver().update(uri, values, null, null);
1324            }
1325        } catch (SQLiteException ex) {
1326        }
1327    }
1328
1329    // Make sure there are at least 5 items after the currently playing item
1330    // and no more than 10 items before.
1331    private void doAutoShuffleUpdate() {
1332        boolean notify = false;
1333        // remove old entries
1334        if (mPlayPos > 10) {
1335            removeTracks(0, mPlayPos - 9);
1336            notify = true;
1337        }
1338        // add new entries if needed
1339        int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1340        for (int i = 0; i < to_add; i++) {
1341            // pick something at random from the list
1342            int idx = mRand.nextInt(mAutoShuffleList.length);
1343            long which = mAutoShuffleList[idx];
1344            ensurePlayListCapacity(mPlayListLen + 1);
1345            mPlayList[mPlayListLen++] = which;
1346            notify = true;
1347        }
1348        if (notify) {
1349            notifyChange(QUEUE_CHANGED);
1350        }
1351    }
1352
1353    // A simple variation of Random that makes sure that the
1354    // value it returns is not equal to the value it returned
1355    // previously, unless the interval is 1.
1356    private static class Shuffler {
1357        private int mPrevious;
1358        private Random mRandom = new Random();
1359        public int nextInt(int interval) {
1360            int ret;
1361            do {
1362                ret = mRandom.nextInt(interval);
1363            } while (ret == mPrevious && interval > 1);
1364            mPrevious = ret;
1365            return ret;
1366        }
1367    };
1368
1369    private boolean makeAutoShuffleList() {
1370        ContentResolver res = getContentResolver();
1371        Cursor c = null;
1372        try {
1373            c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1374                    new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1375                    null, null);
1376            if (c == null || c.getCount() == 0) {
1377                return false;
1378            }
1379            int len = c.getCount();
1380            long [] list = new long[len];
1381            for (int i = 0; i < len; i++) {
1382                c.moveToNext();
1383                list[i] = c.getLong(0);
1384            }
1385            mAutoShuffleList = list;
1386            return true;
1387        } catch (RuntimeException ex) {
1388        } finally {
1389            if (c != null) {
1390                c.close();
1391            }
1392        }
1393        return false;
1394    }
1395
1396    /**
1397     * Removes the range of tracks specified from the play list. If a file within the range is
1398     * the file currently being played, playback will move to the next file after the
1399     * range.
1400     * @param first The first file to be removed
1401     * @param last The last file to be removed
1402     * @return the number of tracks deleted
1403     */
1404    public int removeTracks(int first, int last) {
1405        int numremoved = removeTracksInternal(first, last);
1406        if (numremoved > 0) {
1407            notifyChange(QUEUE_CHANGED);
1408        }
1409        return numremoved;
1410    }
1411
1412    private int removeTracksInternal(int first, int last) {
1413        synchronized (this) {
1414            if (last < first) return 0;
1415            if (first < 0) first = 0;
1416            if (last >= mPlayListLen) last = mPlayListLen - 1;
1417
1418            boolean gotonext = false;
1419            if (first <= mPlayPos && mPlayPos <= last) {
1420                mPlayPos = first;
1421                gotonext = true;
1422            } else if (mPlayPos > last) {
1423                mPlayPos -= (last - first + 1);
1424            }
1425            int num = mPlayListLen - last - 1;
1426            for (int i = 0; i < num; i++) {
1427                mPlayList[first + i] = mPlayList[last + 1 + i];
1428            }
1429            mPlayListLen -= last - first + 1;
1430
1431            if (gotonext) {
1432                if (mPlayListLen == 0) {
1433                    stop(true);
1434                    mPlayPos = -1;
1435                } else {
1436                    if (mPlayPos >= mPlayListLen) {
1437                        mPlayPos = 0;
1438                    }
1439                    boolean wasPlaying = isPlaying();
1440                    stop(false);
1441                    openCurrent();
1442                    if (wasPlaying) {
1443                        play();
1444                    }
1445                }
1446            }
1447            return last - first + 1;
1448        }
1449    }
1450
1451    /**
1452     * Removes all instances of the track with the given id
1453     * from the playlist.
1454     * @param id The id to be removed
1455     * @return how many instances of the track were removed
1456     */
1457    public int removeTrack(long id) {
1458        int numremoved = 0;
1459        synchronized (this) {
1460            for (int i = 0; i < mPlayListLen; i++) {
1461                if (mPlayList[i] == id) {
1462                    numremoved += removeTracksInternal(i, i);
1463                    i--;
1464                }
1465            }
1466        }
1467        if (numremoved > 0) {
1468            notifyChange(QUEUE_CHANGED);
1469        }
1470        return numremoved;
1471    }
1472
1473    public void setShuffleMode(int shufflemode) {
1474        synchronized(this) {
1475            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1476                return;
1477            }
1478            mShuffleMode = shufflemode;
1479            if (mShuffleMode == SHUFFLE_AUTO) {
1480                if (makeAutoShuffleList()) {
1481                    mPlayListLen = 0;
1482                    doAutoShuffleUpdate();
1483                    mPlayPos = 0;
1484                    openCurrent();
1485                    play();
1486                    notifyChange(META_CHANGED);
1487                    return;
1488                } else {
1489                    // failed to build a list of files to shuffle
1490                    mShuffleMode = SHUFFLE_NONE;
1491                }
1492            }
1493            saveQueue(false);
1494        }
1495    }
1496    public int getShuffleMode() {
1497        return mShuffleMode;
1498    }
1499
1500    public void setRepeatMode(int repeatmode) {
1501        synchronized(this) {
1502            mRepeatMode = repeatmode;
1503            saveQueue(false);
1504        }
1505    }
1506    public int getRepeatMode() {
1507        return mRepeatMode;
1508    }
1509
1510    public int getMediaMountedCount() {
1511        return mMediaMountedCount;
1512    }
1513
1514    /**
1515     * Returns the path of the currently playing file, or null if
1516     * no file is currently playing.
1517     */
1518    public String getPath() {
1519        return mFileToPlay;
1520    }
1521
1522    /**
1523     * Returns the rowid of the currently playing file, or -1 if
1524     * no file is currently playing.
1525     */
1526    public long getAudioId() {
1527        synchronized (this) {
1528            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1529                return mPlayList[mPlayPos];
1530            }
1531        }
1532        return -1;
1533    }
1534
1535    /**
1536     * Returns the position in the queue
1537     * @return the position in the queue
1538     */
1539    public int getQueuePosition() {
1540        synchronized(this) {
1541            return mPlayPos;
1542        }
1543    }
1544
1545    /**
1546     * Starts playing the track at the given position in the queue.
1547     * @param pos The position in the queue of the track that will be played.
1548     */
1549    public void setQueuePosition(int pos) {
1550        synchronized(this) {
1551            stop(false);
1552            mPlayPos = pos;
1553            openCurrent();
1554            play();
1555            notifyChange(META_CHANGED);
1556        }
1557    }
1558
1559    public String getArtistName() {
1560        synchronized(this) {
1561            if (mCursor == null) {
1562                return null;
1563            }
1564            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1565        }
1566    }
1567
1568    public long getArtistId() {
1569        synchronized (this) {
1570            if (mCursor == null) {
1571                return -1;
1572            }
1573            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1574        }
1575    }
1576
1577    public String getAlbumName() {
1578        synchronized (this) {
1579            if (mCursor == null) {
1580                return null;
1581            }
1582            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1583        }
1584    }
1585
1586    public long getAlbumId() {
1587        synchronized (this) {
1588            if (mCursor == null) {
1589                return -1;
1590            }
1591            return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1592        }
1593    }
1594
1595    public String getTrackName() {
1596        synchronized (this) {
1597            if (mCursor == null) {
1598                return null;
1599            }
1600            return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1601        }
1602    }
1603
1604    private boolean isPodcast() {
1605        synchronized (this) {
1606            if (mCursor == null) {
1607                return false;
1608            }
1609            return (mCursor.getInt(PODCASTCOLIDX) > 0);
1610        }
1611    }
1612
1613    private long getBookmark() {
1614        synchronized (this) {
1615            if (mCursor == null) {
1616                return 0;
1617            }
1618            return mCursor.getLong(BOOKMARKCOLIDX);
1619        }
1620    }
1621
1622    /**
1623     * Returns the duration of the file in milliseconds.
1624     * Currently this method returns -1 for the duration of MIDI files.
1625     */
1626    public long duration() {
1627        if (mPlayer.isInitialized()) {
1628            return mPlayer.duration();
1629        }
1630        return -1;
1631    }
1632
1633    /**
1634     * Returns the current playback position in milliseconds
1635     */
1636    public long position() {
1637        if (mPlayer.isInitialized()) {
1638            return mPlayer.position();
1639        }
1640        return -1;
1641    }
1642
1643    /**
1644     * Seeks to the position specified.
1645     *
1646     * @param pos The position to seek to, in milliseconds
1647     */
1648    public long seek(long pos) {
1649        if (mPlayer.isInitialized()) {
1650            if (pos < 0) pos = 0;
1651            if (pos > mPlayer.duration()) pos = mPlayer.duration();
1652            return mPlayer.seek(pos);
1653        }
1654        return -1;
1655    }
1656
1657    /**
1658     * Provides a unified interface for dealing with midi files and
1659     * other media files.
1660     */
1661    private class MultiPlayer {
1662        private MediaPlayer mMediaPlayer = new MediaPlayer();
1663        private Handler mHandler;
1664        private boolean mIsInitialized = false;
1665
1666        public MultiPlayer() {
1667            mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1668        }
1669
1670        public void setDataSourceAsync(String path) {
1671            try {
1672                mMediaPlayer.reset();
1673                mMediaPlayer.setDataSource(path);
1674                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1675                mMediaPlayer.setOnPreparedListener(preparedlistener);
1676                mMediaPlayer.prepareAsync();
1677            } catch (IOException ex) {
1678                // TODO: notify the user why the file couldn't be opened
1679                mIsInitialized = false;
1680                return;
1681            } catch (IllegalArgumentException ex) {
1682                // TODO: notify the user why the file couldn't be opened
1683                mIsInitialized = false;
1684                return;
1685            }
1686            mMediaPlayer.setOnCompletionListener(listener);
1687            mMediaPlayer.setOnErrorListener(errorListener);
1688
1689            mIsInitialized = true;
1690        }
1691
1692        public void setDataSource(String path) {
1693            try {
1694                mMediaPlayer.reset();
1695                mMediaPlayer.setOnPreparedListener(null);
1696                if (path.startsWith("content://")) {
1697                    mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1698                } else {
1699                    mMediaPlayer.setDataSource(path);
1700                }
1701                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1702                mMediaPlayer.prepare();
1703            } catch (IOException ex) {
1704                // TODO: notify the user why the file couldn't be opened
1705                mIsInitialized = false;
1706                return;
1707            } catch (IllegalArgumentException ex) {
1708                // TODO: notify the user why the file couldn't be opened
1709                mIsInitialized = false;
1710                return;
1711            }
1712            mMediaPlayer.setOnCompletionListener(listener);
1713            mMediaPlayer.setOnErrorListener(errorListener);
1714
1715            mIsInitialized = true;
1716        }
1717
1718        public boolean isInitialized() {
1719            return mIsInitialized;
1720        }
1721
1722        public void start() {
1723            mMediaPlayer.start();
1724        }
1725
1726        public void stop() {
1727            mMediaPlayer.reset();
1728            mIsInitialized = false;
1729        }
1730
1731        /**
1732         * You CANNOT use this player anymore after calling release()
1733         */
1734        public void release() {
1735            stop();
1736            mMediaPlayer.release();
1737        }
1738
1739        public void pause() {
1740            mMediaPlayer.pause();
1741        }
1742
1743        public void setHandler(Handler handler) {
1744            mHandler = handler;
1745        }
1746
1747        MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1748            public void onCompletion(MediaPlayer mp) {
1749                // Acquire a temporary wakelock, since when we return from
1750                // this callback the MediaPlayer will release its wakelock
1751                // and allow the device to go to sleep.
1752                // This temporary wakelock is released when the RELEASE_WAKELOCK
1753                // message is processed, but just in case, put a timeout on it.
1754                mWakeLock.acquire(30000);
1755                mHandler.sendEmptyMessage(TRACK_ENDED);
1756                mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1757            }
1758        };
1759
1760        MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1761            public void onPrepared(MediaPlayer mp) {
1762                notifyChange(ASYNC_OPEN_COMPLETE);
1763            }
1764        };
1765
1766        MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1767            public boolean onError(MediaPlayer mp, int what, int extra) {
1768                switch (what) {
1769                case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1770                    mIsInitialized = false;
1771                    mMediaPlayer.release();
1772                    // Creating a new MediaPlayer and settings its wakemode does not
1773                    // require the media service, so it's OK to do this now, while the
1774                    // service is still being restarted
1775                    mMediaPlayer = new MediaPlayer();
1776                    mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1777                    mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1778                    return true;
1779                default:
1780                    Log.d("MultiPlayer", "Error: " + what + "," + extra);
1781                    break;
1782                }
1783                return false;
1784           }
1785        };
1786
1787        public long duration() {
1788            return mMediaPlayer.getDuration();
1789        }
1790
1791        public long position() {
1792            return mMediaPlayer.getCurrentPosition();
1793        }
1794
1795        public long seek(long whereto) {
1796            mMediaPlayer.seekTo((int) whereto);
1797            return whereto;
1798        }
1799
1800        public void setVolume(float vol) {
1801            mMediaPlayer.setVolume(vol, vol);
1802        }
1803    }
1804
1805    /*
1806     * By making this a static class with a WeakReference to the Service, we
1807     * ensure that the Service can be GCd even when the system process still
1808     * has a remote reference to the stub.
1809     */
1810    static class ServiceStub extends IMediaPlaybackService.Stub {
1811        WeakReference<MediaPlaybackService> mService;
1812
1813        ServiceStub(MediaPlaybackService service) {
1814            mService = new WeakReference<MediaPlaybackService>(service);
1815        }
1816
1817        public void openFileAsync(String path)
1818        {
1819            mService.get().openAsync(path);
1820        }
1821        public void openFile(String path, boolean oneShot)
1822        {
1823            mService.get().open(path, oneShot);
1824        }
1825        public void open(long [] list, int position) {
1826            mService.get().open(list, position);
1827        }
1828        public int getQueuePosition() {
1829            return mService.get().getQueuePosition();
1830        }
1831        public void setQueuePosition(int index) {
1832            mService.get().setQueuePosition(index);
1833        }
1834        public boolean isPlaying() {
1835            return mService.get().isPlaying();
1836        }
1837        public void stop() {
1838            mService.get().stop();
1839        }
1840        public void pause() {
1841            mService.get().pause();
1842        }
1843        public void play() {
1844            mService.get().play();
1845        }
1846        public void prev() {
1847            mService.get().prev();
1848        }
1849        public void next() {
1850            mService.get().next(true);
1851        }
1852        public String getTrackName() {
1853            return mService.get().getTrackName();
1854        }
1855        public String getAlbumName() {
1856            return mService.get().getAlbumName();
1857        }
1858        public long getAlbumId() {
1859            return mService.get().getAlbumId();
1860        }
1861        public String getArtistName() {
1862            return mService.get().getArtistName();
1863        }
1864        public long getArtistId() {
1865            return mService.get().getArtistId();
1866        }
1867        public void enqueue(long [] list , int action) {
1868            mService.get().enqueue(list, action);
1869        }
1870        public long [] getQueue() {
1871            return mService.get().getQueue();
1872        }
1873        public void moveQueueItem(int from, int to) {
1874            mService.get().moveQueueItem(from, to);
1875        }
1876        public String getPath() {
1877            return mService.get().getPath();
1878        }
1879        public long getAudioId() {
1880            return mService.get().getAudioId();
1881        }
1882        public long position() {
1883            return mService.get().position();
1884        }
1885        public long duration() {
1886            return mService.get().duration();
1887        }
1888        public long seek(long pos) {
1889            return mService.get().seek(pos);
1890        }
1891        public void setShuffleMode(int shufflemode) {
1892            mService.get().setShuffleMode(shufflemode);
1893        }
1894        public int getShuffleMode() {
1895            return mService.get().getShuffleMode();
1896        }
1897        public int removeTracks(int first, int last) {
1898            return mService.get().removeTracks(first, last);
1899        }
1900        public int removeTrack(long id) {
1901            return mService.get().removeTrack(id);
1902        }
1903        public void setRepeatMode(int repeatmode) {
1904            mService.get().setRepeatMode(repeatmode);
1905        }
1906        public int getRepeatMode() {
1907            return mService.get().getRepeatMode();
1908        }
1909        public int getMediaMountedCount() {
1910            return mService.get().getMediaMountedCount();
1911        }
1912
1913    }
1914
1915    private final IBinder mBinder = new ServiceStub(this);
1916}
1917