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