2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.music;
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.appwidget.AppWidgetManager;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.BroadcastReceiver;
31 import android.content.SharedPreferences;
32 import android.content.SharedPreferences.Editor;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteException;
35 import android.media.AudioManager;
36 import android.media.MediaFile;
37 import android.media.MediaPlayer;
38 import android.net.Uri;
39 import android.os.Environment;
40 import android.os.FileUtils;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Message;
44 import android.os.PowerManager;
45 import android.os.SystemClock;
46 import android.os.PowerManager.WakeLock;
47 import android.provider.MediaStore;
48 import android.util.Log;
49 import android.widget.RemoteViews;
50 import android.widget.Toast;
51 import com.android.internal.telephony.Phone;
52 import com.android.internal.telephony.PhoneStateIntentReceiver;
54 import java.io.IOException;
55 import java.util.Random;
56 import java.util.Vector;
59 * Provides "background" audio playback capabilities, allowing the
60 * user to switch between activities without stopping playback.
62 public 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
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;
72 public static final int SHUFFLE_NONE = 0;
73 public static final int SHUFFLE_NORMAL = 1;
74 public static final int SHUFFLE_AUTO = 2;
76 public static final int REPEAT_NONE = 0;
77 public static final int REPEAT_CURRENT = 1;
78 public static final int REPEAT_ALL = 2;
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";
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";
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";
99 private static final int PHONE_CHANGED = 1;
100 private static final int TRACK_ENDED = 1;
101 private static final int RELEASE_WAKELOCK = 2;
102 private static final int SERVER_DIED = 3;
103 private static final int FADEIN = 4;
104 private static final int MAX_HISTORY_SIZE = 10;
106 private MultiPlayer mPlayer;
107 private String mFileToPlay;
108 private PhoneStateIntentReceiver mPsir;
109 private int mShuffleMode = SHUFFLE_NONE;
110 private int mRepeatMode = REPEAT_NONE;
111 private int mMediaMountedCount = 0;
112 private int [] mAutoShuffleList = null;
113 private boolean mOneShot;
114 private int [] mPlayList = null;
115 private int mPlayListLen = 0;
116 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
117 private Cursor mCursor;
118 private int mPlayPos = -1;
119 private static final String LOGTAG = "MediaPlaybackService";
120 private final Shuffler mRand = new Shuffler();
121 private int mOpenFailedCounter = 0;
122 String[] mCursorCols = new String[] {
123 "audio._id AS _id", // index must match IDCOLIDX below
124 MediaStore.Audio.Media.ARTIST,
125 MediaStore.Audio.Media.ALBUM,
126 MediaStore.Audio.Media.TITLE,
127 MediaStore.Audio.Media.DATA,
128 MediaStore.Audio.Media.MIME_TYPE,
129 MediaStore.Audio.Media.ALBUM_ID,
130 MediaStore.Audio.Media.ARTIST_ID,
131 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
132 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
134 private final static int IDCOLIDX = 0;
135 private final static int PODCASTCOLIDX = 8;
136 private final static int BOOKMARKCOLIDX = 9;
137 private BroadcastReceiver mUnmountReceiver = null;
138 private WakeLock mWakeLock;
139 private int mServiceStartId = -1;
140 private boolean mServiceInUse = false;
141 private boolean mResumeAfterCall = false;
142 private boolean mIsSupposedToBePlaying = false;
143 private boolean mQuietMode = false;
145 private SharedPreferences mPreferences;
146 // We use this to distinguish between different cards when saving/restoring playlists.
147 // This will have to change if we want to support multiple simultaneous cards.
150 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
152 // interval after which we stop the service when idle
153 private static final int IDLE_DELAY = 60000;
155 private Handler mPhoneHandler = new Handler() {
157 public void handleMessage(Message msg) {
160 Phone.State state = mPsir.getPhoneState();
161 if (state == Phone.State.RINGING) {
162 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
163 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
164 if (ringvolume > 0) {
165 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
168 } else if (state == Phone.State.OFFHOOK) {
169 // pause the music while a conversation is in progress
170 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
172 } else if (state == Phone.State.IDLE) {
173 // start playing again
174 if (mResumeAfterCall) {
175 // resume playback only if music was playing
176 // when the call was answered
178 mResumeAfterCall = false;
188 private void startAndFadeIn() {
189 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
192 private Handler mMediaplayerHandler = new Handler() {
193 float mCurrentVolume = 1.0f;
195 public void handleMessage(Message msg) {
200 mPlayer.setVolume(mCurrentVolume);
202 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
204 mCurrentVolume += 0.01f;
205 if (mCurrentVolume < 1.0f) {
206 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
208 mCurrentVolume = 1.0f;
210 mPlayer.setVolume(mCurrentVolume);
214 if (mIsSupposedToBePlaying) {
217 // the server died when we were idle, so just
218 // reopen the same song (it will start again
219 // from the beginning though when the user
225 if (mRepeatMode == REPEAT_CURRENT) {
228 } else if (!mOneShot) {
231 notifyChange(PLAYBACK_COMPLETE);
232 mIsSupposedToBePlaying = false;
235 case RELEASE_WAKELOCK:
244 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
246 public void onReceive(Context context, Intent intent) {
247 String action = intent.getAction();
248 String cmd = intent.getStringExtra("command");
249 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
251 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
253 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
259 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
261 } else if (CMDSTOP.equals(cmd)) {
264 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
265 // Someone asked us to refresh a set of specific widgets, probably
266 // because they were just added.
267 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
268 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
273 public MediaPlaybackService() {
274 mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
275 mPsir.notifyPhoneCallState(PHONE_CHANGED);
279 public void onCreate() {
282 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
283 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
285 registerExternalStorageListener();
287 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
288 mPlayer = new MultiPlayer();
289 mPlayer.setHandler(mMediaplayerHandler);
291 // Clear leftover notification in case this service previously got killed while playing
292 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
293 nm.cancel(PLAYBACKSERVICE_STATUS);
297 IntentFilter commandFilter = new IntentFilter();
298 commandFilter.addAction(SERVICECMD);
299 commandFilter.addAction(TOGGLEPAUSE_ACTION);
300 commandFilter.addAction(PAUSE_ACTION);
301 commandFilter.addAction(NEXT_ACTION);
302 commandFilter.addAction(PREVIOUS_ACTION);
303 registerReceiver(mIntentReceiver, commandFilter);
305 mPsir.registerIntent();
306 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
307 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
308 mWakeLock.setReferenceCounted(false);
310 // If the service was idle, but got killed before it stopped itself, the
311 // system will relaunch it. Make sure it gets stopped again in that case.
312 Message msg = mDelayedStopHandler.obtainMessage();
313 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
317 public void onDestroy() {
318 // Check that we're not being destroyed while something is still playing.
320 Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
322 // and for good measure, call mPlayer.stop(), which calls MediaPlayer.reset(), which
323 // releases the MediaPlayer's wake lock, if any.
326 if (mCursor != null) {
331 unregisterReceiver(mIntentReceiver);
332 if (mUnmountReceiver != null) {
333 unregisterReceiver(mUnmountReceiver);
334 mUnmountReceiver = null;
336 mPsir.unregisterIntent();
341 private final char hexdigits [] = new char [] {
348 private void saveQueue(boolean full) {
352 Editor ed = mPreferences.edit();
353 //long start = System.currentTimeMillis();
355 StringBuilder q = new StringBuilder();
357 // The current playlist is saved as a list of "reverse hexadecimal"
358 // numbers, which we can generate faster than normal decimal or
359 // hexadecimal numbers, which in turn allows us to save the playlist
360 // more often without worrying too much about performance.
361 // (saving the full state takes about 40 ms under no-load conditions
363 int len = mPlayListLen;
364 for (int i = 0; i < len; i++) {
365 int n = mPlayList[i];
372 q.append(hexdigits[digit]);
377 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
378 ed.putString("queue", q.toString());
379 ed.putInt("cardid", mCardId);
381 ed.putInt("curpos", mPlayPos);
382 if (mPlayer.isInitialized()) {
383 ed.putLong("seekpos", mPlayer.position());
385 ed.putInt("repeatmode", mRepeatMode);
386 ed.putInt("shufflemode", mShuffleMode);
389 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
392 private void reloadQueue() {
395 boolean newstyle = false;
397 if (mPreferences.contains("cardid")) {
399 id = mPreferences.getInt("cardid", ~mCardId);
402 // Only restore the saved playlist if the card is still
403 // the same one as when the playlist was saved
404 q = mPreferences.getString("queue", "");
406 if (q != null && q.length() > 1) {
407 //Log.i("@@@@ service", "loaded queue: " + q);
408 String [] entries = q.split(";");
409 int len = entries.length;
410 ensurePlayListCapacity(len);
411 for (int i = 0; i < len; i++) {
413 String revhex = entries[i];
415 for (int j = revhex.length() - 1; j >= 0 ; j--) {
417 char c = revhex.charAt(j);
418 if (c >= '0' && c <= '9') {
420 } else if (c >= 'a' && c <= 'f') {
423 // bogus playlist data
430 mPlayList[i] = Integer.parseInt(entries[i]);
435 int pos = mPreferences.getInt("curpos", 0);
436 if (pos < 0 || pos >= len) {
437 // The saved playlist is bogus, discard it
443 // When reloadQueue is called in response to a card-insertion,
444 // we might not be able to query the media provider right away.
445 // To deal with this, try querying for the current file, and if
446 // that fails, wait a while and try again. If that too fails,
447 // assume there is a problem and don't restore the state.
448 Cursor c = MusicUtils.query(this,
449 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
450 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
451 if (c == null || c.getCount() == 0) {
452 // wait a bit and try again
453 SystemClock.sleep(3000);
454 c = getContentResolver().query(
455 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
456 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
462 // Make sure we don't auto-skip to the next song, since that
463 // also starts playback. What could happen in that case is:
465 // - go to UMS and delete some files, including the currently playing one
466 // - come back from UMS
468 // - music app is killed for some reason (out of memory)
469 // - music service is restarted, service restores state, doesn't find
470 // the "current" file, goes to the next and: playback starts on its
471 // own, potentially at some random inconvenient time.
472 mOpenFailedCounter = 20;
476 if (!mPlayer.isInitialized()) {
477 // couldn't restore the saved state
482 long seekpos = mPreferences.getLong("seekpos", 0);
483 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
485 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
486 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
487 repmode = REPEAT_NONE;
489 mRepeatMode = repmode;
491 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
492 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
493 shufmode = SHUFFLE_NONE;
495 if (shufmode == SHUFFLE_AUTO) {
496 if (! makeAutoShuffleList()) {
497 shufmode = SHUFFLE_NONE;
500 mShuffleMode = shufmode;
505 public IBinder onBind(Intent intent) {
506 mDelayedStopHandler.removeCallbacksAndMessages(null);
507 mServiceInUse = true;
512 public void onRebind(Intent intent) {
513 mDelayedStopHandler.removeCallbacksAndMessages(null);
514 mServiceInUse = true;
518 public void onStart(Intent intent, int startId) {
519 mServiceStartId = startId;
520 mDelayedStopHandler.removeCallbacksAndMessages(null);
522 String action = intent.getAction();
523 String cmd = intent.getStringExtra("command");
525 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
527 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
529 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
535 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
537 } else if (CMDSTOP.equals(cmd)) {
542 // make sure the service will shut down on its own if it was
543 // just started but not bound to and nothing is playing
544 mDelayedStopHandler.removeCallbacksAndMessages(null);
545 Message msg = mDelayedStopHandler.obtainMessage();
546 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
550 public boolean onUnbind(Intent intent) {
551 mServiceInUse = false;
553 // Take a snapshot of the current playlist
556 if (isPlaying() || mResumeAfterCall) {
557 // something is currently playing, or will be playing once
558 // an in-progress call ends, so don't stop the service now.
562 // If there is a playlist but playback is paused, then wait a while
563 // before stopping the service, so that pause/resume isn't slow.
564 // Also delay stopping the service if we're transitioning between tracks.
565 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
566 Message msg = mDelayedStopHandler.obtainMessage();
567 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
571 // No active playlist, OK to stop the service right now
572 stopSelf(mServiceStartId);
576 private Handler mDelayedStopHandler = new Handler() {
578 public void handleMessage(Message msg) {
579 // Check again to make sure nothing is playing right now
580 if (isPlaying() || mResumeAfterCall || mServiceInUse
581 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
584 // save the queue again, because it might have changed
585 // since the user exited the music app (because of
586 // party-shuffle or because the play-position changed)
588 stopSelf(mServiceStartId);
593 * Called when we receive a ACTION_MEDIA_EJECT notification.
595 * @param storagePath path to mount point for the removed media
597 public void closeExternalStorageFiles(String storagePath) {
598 // stop playback and clean up if the SD card is going to be unmounted.
600 notifyChange(QUEUE_CHANGED);
601 notifyChange(META_CHANGED);
605 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
606 * The intent will call closeExternalStorageFiles() if the external media
607 * is going to be ejected, so applications can clean up any files they have open.
609 public void registerExternalStorageListener() {
610 if (mUnmountReceiver == null) {
611 mUnmountReceiver = new BroadcastReceiver() {
613 public void onReceive(Context context, Intent intent) {
614 String action = intent.getAction();
615 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
617 mOneShot = true; // This makes us not save the state again later,
618 // which would be wrong because the song ids and
619 // card id might not match.
620 closeExternalStorageFiles(intent.getData().getPath());
621 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
622 mMediaMountedCount++;
623 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
625 notifyChange(QUEUE_CHANGED);
626 notifyChange(META_CHANGED);
630 IntentFilter iFilter = new IntentFilter();
631 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
632 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
633 iFilter.addDataScheme("file");
634 registerReceiver(mUnmountReceiver, iFilter);
639 * Notify the change-receivers that something has changed.
640 * The intent that is sent contains the following data
641 * for the currently playing track:
642 * "id" - Integer: the database row ID
643 * "artist" - String: the name of the artist
644 * "album" - String: the name of the album
645 * "track" - String: the name of the track
646 * The intent has an action that is one of
647 * "com.android.music.metachanged"
648 * "com.android.music.queuechanged",
649 * "com.android.music.playbackcomplete"
650 * "com.android.music.playstatechanged"
651 * respectively indicating that a new track has
652 * started playing, that the playback queue has
653 * changed, that playback has stopped because
654 * the last file in the list has been played,
655 * or that the play-state changed (paused/resumed).
657 private void notifyChange(String what) {
659 Intent i = new Intent(what);
660 i.putExtra("id", Integer.valueOf(getAudioId()));
661 i.putExtra("artist", getArtistName());
662 i.putExtra("album",getAlbumName());
663 i.putExtra("track", getTrackName());
666 if (what.equals(QUEUE_CHANGED)) {
672 // Share this notification directly with our widgets
673 mAppWidgetProvider.notifyChange(this, what);
676 private void ensurePlayListCapacity(int size) {
677 if (mPlayList == null || size > mPlayList.length) {
678 // reallocate at 2x requested size so we don't
679 // need to grow and copy the array for every
681 int [] newlist = new int[size * 2];
682 int len = mPlayListLen;
683 for (int i = 0; i < len; i++) {
684 newlist[i] = mPlayList[i];
688 // FIXME: shrink the array when the needed size is much smaller
689 // than the allocated size
692 // insert the list of songs at the specified position in the playlist
693 private void addToPlayList(int [] list, int position) {
694 int addlen = list.length;
695 if (position < 0) { // overwrite
699 ensurePlayListCapacity(mPlayListLen + addlen);
700 if (position > mPlayListLen) {
701 position = mPlayListLen;
704 // move part of list after insertion point
705 int tailsize = mPlayListLen - position;
706 for (int i = tailsize ; i > 0 ; i--) {
707 mPlayList[position + i] = mPlayList[position + i - addlen];
710 // copy list into playlist
711 for (int i = 0; i < addlen; i++) {
712 mPlayList[position + i] = list[i];
714 mPlayListLen += addlen;
718 * Appends a list of tracks to the current playlist.
719 * If nothing is playing currently, playback will be started at
721 * If the action is NOW, playback will switch to the first of
722 * the new tracks immediately.
723 * @param list The list of tracks to append.
724 * @param action NOW, NEXT or LAST
726 public void enqueue(int [] list, int action) {
728 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
729 addToPlayList(list, mPlayPos + 1);
730 notifyChange(QUEUE_CHANGED);
732 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
733 addToPlayList(list, Integer.MAX_VALUE);
734 notifyChange(QUEUE_CHANGED);
736 mPlayPos = mPlayListLen - list.length;
739 notifyChange(META_CHANGED);
747 notifyChange(META_CHANGED);
753 * Replaces the current playlist with a new list,
754 * and prepares for starting playback at the specified
755 * position in the list, or a random position if the
756 * specified position is 0.
757 * @param list The new list of tracks.
759 public void open(int [] list, int position) {
760 synchronized (this) {
761 if (mShuffleMode == SHUFFLE_AUTO) {
762 mShuffleMode = SHUFFLE_NORMAL;
764 int oldId = getAudioId();
765 int listlength = list.length;
766 boolean newlist = true;
767 if (mPlayListLen == listlength) {
768 // possible fast path: list might be the same
770 for (int i = 0; i < listlength; i++) {
771 if (list[i] != mPlayList[i]) {
778 addToPlayList(list, -1);
779 notifyChange(QUEUE_CHANGED);
781 int oldpos = mPlayPos;
785 mPlayPos = mRand.nextInt(mPlayListLen);
789 saveBookmarkIfNeeded();
791 if (oldId != getAudioId()) {
792 notifyChange(META_CHANGED);
798 * Moves the item at index1 to index2.
802 public void moveQueueItem(int index1, int index2) {
803 synchronized (this) {
804 if (index1 >= mPlayListLen) {
805 index1 = mPlayListLen - 1;
807 if (index2 >= mPlayListLen) {
808 index2 = mPlayListLen - 1;
810 if (index1 < index2) {
811 int tmp = mPlayList[index1];
812 for (int i = index1; i < index2; i++) {
813 mPlayList[i] = mPlayList[i+1];
815 mPlayList[index2] = tmp;
816 if (mPlayPos == index1) {
818 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
821 } else if (index2 < index1) {
822 int tmp = mPlayList[index1];
823 for (int i = index1; i > index2; i--) {
824 mPlayList[i] = mPlayList[i-1];
826 mPlayList[index2] = tmp;
827 if (mPlayPos == index1) {
829 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
833 notifyChange(QUEUE_CHANGED);
838 * Returns the current play list
839 * @return An array of integers containing the IDs of the tracks in the play list
841 public int [] getQueue() {
842 synchronized (this) {
843 int len = mPlayListLen;
844 int [] list = new int[len];
845 for (int i = 0; i < len; i++) {
846 list[i] = mPlayList[i];
852 private void openCurrent() {
853 synchronized (this) {
854 if (mCursor != null) {
858 if (mPlayListLen == 0) {
863 String id = String.valueOf(mPlayList[mPlayPos]);
865 mCursor = getContentResolver().query(
866 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
867 mCursorCols, "_id=" + id , null, null);
868 if (mCursor != null) {
869 mCursor.moveToFirst();
870 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
871 // go to bookmark if needed
873 long bookmark = getBookmark();
874 // Start playing a little bit before the bookmark,
875 // so it's easier to get back in to the narrative.
876 seek(bookmark - 5000);
882 public void openAsync(String path) {
883 synchronized (this) {
888 mRepeatMode = REPEAT_NONE;
889 ensurePlayListCapacity(1);
895 mPlayer.setDataSourceAsync(mFileToPlay);
901 * Opens the specified file and readies it for playback.
903 * @param path The full path of the file to be opened.
904 * @param oneshot when set to true, playback will stop after this file completes, instead
905 * of moving on to the next track in the list
907 public void open(String path, boolean oneshot) {
908 synchronized (this) {
914 mRepeatMode = REPEAT_NONE;
915 ensurePlayListCapacity(1);
920 // if mCursor is null, try to associate path with a database cursor
921 if (mCursor == null) {
923 ContentResolver resolver = getContentResolver();
926 String selectionArgs[];
927 if (path.startsWith("content://media/")) {
928 uri = Uri.parse(path);
930 selectionArgs = null;
932 uri = MediaStore.Audio.Media.getContentUriForPath(path);
933 where = MediaStore.Audio.Media.DATA + "=?";
934 selectionArgs = new String[] { path };
938 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
939 if (mCursor != null) {
940 if (mCursor.getCount() == 0) {
944 mCursor.moveToNext();
945 ensurePlayListCapacity(1);
947 mPlayList[0] = mCursor.getInt(IDCOLIDX);
951 } catch (UnsupportedOperationException ex) {
955 mPlayer.setDataSource(mFileToPlay);
957 if (! mPlayer.isInitialized()) {
959 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
960 // beware: this ends up being recursive because next() calls open() again.
963 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
964 // need to make sure we only shows this once
965 mOpenFailedCounter = 0;
967 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
971 mOpenFailedCounter = 0;
977 * Starts playback of a previously opened file.
980 if (mPlayer.isInitialized()) {
984 NotificationManager nm = (NotificationManager)
985 getSystemService(Context.NOTIFICATION_SERVICE);
987 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
988 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
989 if (getAudioId() < 0) {
991 views.setTextViewText(R.id.trackname, getPath());
992 views.setTextViewText(R.id.artistalbum, null);
994 String artist = getArtistName();
995 views.setTextViewText(R.id.trackname, getTrackName());
996 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
997 artist = getString(R.string.unknown_artist_name);
999 String album = getAlbumName();
1000 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1001 album = getString(R.string.unknown_album_name);
1004 views.setTextViewText(R.id.artistalbum,
1005 getString(R.string.notification_artist_album, artist, album)
1009 Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
1010 statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1011 Notification status = new Notification();
1012 status.contentView = views;
1013 status.flags |= Notification.FLAG_ONGOING_EVENT;
1014 status.icon = R.drawable.stat_notify_musicplayer;
1015 status.contentIntent = PendingIntent.getActivity(this, 0,
1016 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1017 nm.notify(PLAYBACKSERVICE_STATUS, status);
1018 if (!mIsSupposedToBePlaying) {
1019 notifyChange(PLAYSTATE_CHANGED);
1021 mIsSupposedToBePlaying = true;
1022 } else if (mPlayListLen <= 0) {
1023 // This is mostly so that if you press 'play' on a bluetooth headset
1024 // without every having played anything before, it will still play
1026 setShuffleMode(SHUFFLE_AUTO);
1030 private void stop(boolean remove_status_icon) {
1031 if (mPlayer.isInitialized()) {
1035 if (mCursor != null) {
1039 if (remove_status_icon) {
1042 setForeground(false);
1043 if (remove_status_icon) {
1044 mIsSupposedToBePlaying = false;
1051 public void stop() {
1056 * Pauses playback (call play() to resume)
1058 public void pause() {
1059 synchronized(this) {
1063 setForeground(false);
1064 mIsSupposedToBePlaying = false;
1065 notifyChange(PLAYSTATE_CHANGED);
1066 saveBookmarkIfNeeded();
1071 /** Returns whether something is currently playing
1073 * @return true if something is playing (or will be playing shortly, in case
1074 * we're currently transitioning between tracks), false if not.
1076 public boolean isPlaying() {
1077 return mIsSupposedToBePlaying;
1081 Desired behavior for prev/next/shuffle:
1083 - NEXT will move to the next track in the list when not shuffling, and to
1084 a track randomly picked from the not-yet-played tracks when shuffling.
1085 If all tracks have already been played, pick from the full set, but
1086 avoid picking the previously played track if possible.
1087 - when shuffling, PREV will go to the previously played track. Hitting PREV
1088 again will go to the track played before that, etc. When the start of the
1089 history has been reached, PREV is a no-op.
1090 When not shuffling, PREV will go to the sequentially previous track (the
1091 difference with the shuffle-case is mainly that when not shuffling, the
1092 user can back up to tracks that are not in the history).
1095 When playing an album with 10 tracks from the start, and enabling shuffle
1096 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1097 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1098 When hitting 'prev' 8 times while playing track 7 in this example, the
1099 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1100 a random track will be picked again. If at any time user disables shuffling
1101 the next/previous track will be picked in sequential order again.
1104 public void prev() {
1105 synchronized (this) {
1107 // we were playing a specific file not part of a playlist, so there is no 'previous'
1112 if (mShuffleMode == SHUFFLE_NORMAL) {
1113 // go to previously-played track and remove it from the history
1114 int histsize = mHistory.size();
1115 if (histsize == 0) {
1119 Integer pos = mHistory.remove(histsize - 1);
1120 mPlayPos = pos.intValue();
1125 mPlayPos = mPlayListLen - 1;
1128 saveBookmarkIfNeeded();
1132 notifyChange(META_CHANGED);
1136 public void next(boolean force) {
1137 synchronized (this) {
1139 // we were playing a specific file not part of a playlist, so there is no 'next'
1145 // Store the current file in the history, but keep the history at a
1147 if (mPlayPos >= 0) {
1148 mHistory.add(Integer.valueOf(mPlayPos));
1150 if (mHistory.size() > MAX_HISTORY_SIZE) {
1151 mHistory.removeElementAt(0);
1154 if (mShuffleMode == SHUFFLE_NORMAL) {
1155 // Pick random next track from the not-yet-played ones
1156 // TODO: make it work right after adding/removing items in the queue.
1158 int numTracks = mPlayListLen;
1159 int[] tracks = new int[numTracks];
1160 for (int i=0;i < numTracks; i++) {
1164 int numHistory = mHistory.size();
1165 int numUnplayed = numTracks;
1166 for (int i=0;i < numHistory; i++) {
1167 int idx = mHistory.get(i).intValue();
1168 if (idx < numTracks && tracks[idx] >= 0) {
1174 // 'numUnplayed' now indicates how many tracks have not yet
1175 // been played, and 'tracks' contains the indices of those
1177 if (numUnplayed <=0) {
1178 // everything's already been played
1179 if (mRepeatMode == REPEAT_ALL || force) {
1180 //pick from full set
1181 numUnplayed = numTracks;
1182 for (int i=0;i < numTracks; i++) {
1191 int skip = mRand.nextInt(numUnplayed);
1194 while (tracks[++cnt] < 0)
1202 } else if (mShuffleMode == SHUFFLE_AUTO) {
1203 doAutoShuffleUpdate();
1206 if (mPlayPos >= mPlayListLen - 1) {
1207 // we're at the end of the list
1208 if (mRepeatMode == REPEAT_NONE && !force) {
1211 notifyChange(PLAYBACK_COMPLETE);
1212 mIsSupposedToBePlaying = false;
1214 } else if (mRepeatMode == REPEAT_ALL || force) {
1221 saveBookmarkIfNeeded();
1225 notifyChange(META_CHANGED);
1229 private void gotoIdleState() {
1230 NotificationManager nm =
1231 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1232 nm.cancel(PLAYBACKSERVICE_STATUS);
1233 mDelayedStopHandler.removeCallbacksAndMessages(null);
1234 Message msg = mDelayedStopHandler.obtainMessage();
1235 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1238 private void saveBookmarkIfNeeded() {
1241 long pos = position();
1242 long bookmark = getBookmark();
1243 long duration = duration();
1244 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1245 (pos > bookmark && (pos - 10000) < bookmark)) {
1246 // The existing bookmark is close to the current
1247 // position, so don't update it.
1250 if (pos < 15000 || (pos + 10000) > duration) {
1251 // if we're near the start or end, clear the bookmark
1255 // write 'pos' to the bookmark field
1256 ContentValues values = new ContentValues();
1257 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1258 Uri uri = ContentUris.withAppendedId(
1259 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1260 getContentResolver().update(uri, values, null, null);
1262 } catch (SQLiteException ex) {
1266 // Make sure there are at least 5 items after the currently playing item
1267 // and no more than 10 items before.
1268 private void doAutoShuffleUpdate() {
1269 boolean notify = false;
1270 // remove old entries
1271 if (mPlayPos > 10) {
1272 removeTracks(0, mPlayPos - 9);
1275 // add new entries if needed
1276 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1277 for (int i = 0; i < to_add; i++) {
1278 // pick something at random from the list
1279 int idx = mRand.nextInt(mAutoShuffleList.length);
1280 Integer which = mAutoShuffleList[idx];
1281 ensurePlayListCapacity(mPlayListLen + 1);
1282 mPlayList[mPlayListLen++] = which;
1286 notifyChange(QUEUE_CHANGED);
1290 // A simple variation of Random that makes sure that the
1291 // value it returns is not equal to the value it returned
1292 // previously, unless the interval is 1.
1293 private class Shuffler {
1294 private int mPrevious;
1295 private Random mRandom = new Random();
1296 public int nextInt(int interval) {
1299 ret = mRandom.nextInt(interval);
1300 } while (ret == mPrevious && interval > 1);
1306 private boolean makeAutoShuffleList() {
1307 ContentResolver res = getContentResolver();
1310 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1311 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1313 if (c == null || c.getCount() == 0) {
1316 int len = c.getCount();
1317 int[] list = new int[len];
1318 for (int i = 0; i < len; i++) {
1320 list[i] = c.getInt(0);
1322 mAutoShuffleList = list;
1324 } catch (RuntimeException ex) {
1334 * Removes the range of tracks specified from the play list. If a file within the range is
1335 * the file currently being played, playback will move to the next file after the
1337 * @param first The first file to be removed
1338 * @param last The last file to be removed
1339 * @return the number of tracks deleted
1341 public int removeTracks(int first, int last) {
1342 int numremoved = removeTracksInternal(first, last);
1343 if (numremoved > 0) {
1344 notifyChange(QUEUE_CHANGED);
1349 private int removeTracksInternal(int first, int last) {
1350 synchronized (this) {
1351 if (last < first) return 0;
1352 if (first < 0) first = 0;
1353 if (last >= mPlayListLen) last = mPlayListLen - 1;
1355 boolean gotonext = false;
1356 if (first <= mPlayPos && mPlayPos <= last) {
1359 } else if (mPlayPos > last) {
1360 mPlayPos -= (last - first + 1);
1362 int num = mPlayListLen - last - 1;
1363 for (int i = 0; i < num; i++) {
1364 mPlayList[first + i] = mPlayList[last + 1 + i];
1366 mPlayListLen -= last - first + 1;
1369 if (mPlayListLen == 0) {
1373 if (mPlayPos >= mPlayListLen) {
1376 boolean wasPlaying = isPlaying();
1384 return last - first + 1;
1389 * Removes all instances of the track with the given id
1390 * from the playlist.
1391 * @param id The id to be removed
1392 * @return how many instances of the track were removed
1394 public int removeTrack(int id) {
1396 synchronized (this) {
1397 for (int i = 0; i < mPlayListLen; i++) {
1398 if (mPlayList[i] == id) {
1399 numremoved += removeTracksInternal(i, i);
1404 if (numremoved > 0) {
1405 notifyChange(QUEUE_CHANGED);
1410 public void setShuffleMode(int shufflemode) {
1411 synchronized(this) {
1412 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1415 mShuffleMode = shufflemode;
1416 if (mShuffleMode == SHUFFLE_AUTO) {
1417 if (makeAutoShuffleList()) {
1419 doAutoShuffleUpdate();
1423 notifyChange(META_CHANGED);
1426 // failed to build a list of files to shuffle
1427 mShuffleMode = SHUFFLE_NONE;
1433 public int getShuffleMode() {
1434 return mShuffleMode;
1437 public void setRepeatMode(int repeatmode) {
1438 synchronized(this) {
1439 mRepeatMode = repeatmode;
1443 public int getRepeatMode() {
1447 public int getMediaMountedCount() {
1448 return mMediaMountedCount;
1452 * Returns the path of the currently playing file, or null if
1453 * no file is currently playing.
1455 public String getPath() {
1460 * Returns the rowid of the currently playing file, or -1 if
1461 * no file is currently playing.
1463 public int getAudioId() {
1464 synchronized (this) {
1465 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1466 return mPlayList[mPlayPos];
1473 * Returns the position in the queue
1474 * @return the position in the queue
1476 public int getQueuePosition() {
1477 synchronized(this) {
1483 * Starts playing the track at the given position in the queue.
1484 * @param pos The position in the queue of the track that will be played.
1486 public void setQueuePosition(int pos) {
1487 synchronized(this) {
1492 notifyChange(META_CHANGED);
1496 public String getArtistName() {
1497 synchronized(this) {
1498 if (mCursor == null) {
1501 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1505 public int getArtistId() {
1506 synchronized (this) {
1507 if (mCursor == null) {
1510 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1514 public String getAlbumName() {
1515 synchronized (this) {
1516 if (mCursor == null) {
1519 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1523 public int getAlbumId() {
1524 synchronized (this) {
1525 if (mCursor == null) {
1528 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1532 public String getTrackName() {
1533 synchronized (this) {
1534 if (mCursor == null) {
1537 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1541 private boolean isPodcast() {
1542 synchronized (this) {
1543 if (mCursor == null) {
1546 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1550 private long getBookmark() {
1551 synchronized (this) {
1552 if (mCursor == null) {
1555 return mCursor.getLong(BOOKMARKCOLIDX);
1560 * Returns the duration of the file in milliseconds.
1561 * Currently this method returns -1 for the duration of MIDI files.
1563 public long duration() {
1564 if (mPlayer.isInitialized()) {
1565 return mPlayer.duration();
1571 * Returns the current playback position in milliseconds
1573 public long position() {
1574 if (mPlayer.isInitialized()) {
1575 return mPlayer.position();
1581 * Seeks to the position specified.
1583 * @param pos The position to seek to, in milliseconds
1585 public long seek(long pos) {
1586 if (mPlayer.isInitialized()) {
1587 if (pos < 0) pos = 0;
1588 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1589 return mPlayer.seek(pos);
1595 * Provides a unified interface for dealing with midi files and
1596 * other media files.
1598 private class MultiPlayer {
1599 private MediaPlayer mMediaPlayer = new MediaPlayer();
1600 private Handler mHandler;
1601 private boolean mIsInitialized = false;
1603 public MultiPlayer() {
1604 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1607 public void setDataSourceAsync(String path) {
1609 mMediaPlayer.reset();
1610 mMediaPlayer.setDataSource(path);
1611 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1612 mMediaPlayer.setOnPreparedListener(preparedlistener);
1613 mMediaPlayer.prepareAsync();
1614 } catch (IOException ex) {
1615 // TODO: notify the user why the file couldn't be opened
1616 mIsInitialized = false;
1618 } catch (IllegalArgumentException ex) {
1619 // TODO: notify the user why the file couldn't be opened
1620 mIsInitialized = false;
1623 mMediaPlayer.setOnCompletionListener(listener);
1624 mMediaPlayer.setOnErrorListener(errorListener);
1626 mIsInitialized = true;
1629 public void setDataSource(String path) {
1631 mMediaPlayer.reset();
1632 mMediaPlayer.setOnPreparedListener(null);
1633 if (path.startsWith("content://")) {
1634 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1636 mMediaPlayer.setDataSource(path);
1638 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1639 mMediaPlayer.prepare();
1640 } catch (IOException ex) {
1641 // TODO: notify the user why the file couldn't be opened
1642 mIsInitialized = false;
1644 } catch (IllegalArgumentException ex) {
1645 // TODO: notify the user why the file couldn't be opened
1646 mIsInitialized = false;
1649 mMediaPlayer.setOnCompletionListener(listener);
1650 mMediaPlayer.setOnErrorListener(errorListener);
1652 mIsInitialized = true;
1655 public boolean isInitialized() {
1656 return mIsInitialized;
1659 public void start() {
1660 mMediaPlayer.start();
1663 public void stop() {
1664 mMediaPlayer.reset();
1665 mIsInitialized = false;
1668 public void pause() {
1669 mMediaPlayer.pause();
1672 public void setHandler(Handler handler) {
1676 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1677 public void onCompletion(MediaPlayer mp) {
1678 // Acquire a temporary wakelock, since when we return from
1679 // this callback the MediaPlayer will release its wakelock
1680 // and allow the device to go to sleep.
1681 // This temporary wakelock is released when the RELEASE_WAKELOCK
1682 // message is processed, but just in case, put a timeout on it.
1683 mWakeLock.acquire(30000);
1684 mHandler.sendEmptyMessage(TRACK_ENDED);
1685 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1689 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1690 public void onPrepared(MediaPlayer mp) {
1691 notifyChange(ASYNC_OPEN_COMPLETE);
1695 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1696 public boolean onError(MediaPlayer mp, int what, int extra) {
1698 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1699 mIsInitialized = false;
1700 mMediaPlayer.release();
1701 // Creating a new MediaPlayer and settings its wakemode does not
1702 // require the media service, so it's OK to do this now, while the
1703 // service is still being restarted
1704 mMediaPlayer = new MediaPlayer();
1705 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1706 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1715 public long duration() {
1716 return mMediaPlayer.getDuration();
1719 public long position() {
1720 return mMediaPlayer.getCurrentPosition();
1723 public long seek(long whereto) {
1724 mMediaPlayer.seekTo((int) whereto);
1728 public void setVolume(float vol) {
1729 mMediaPlayer.setVolume(vol, vol);
1733 private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
1735 public void openFileAsync(String path)
1737 MediaPlaybackService.this.openAsync(path);
1739 public void openFile(String path, boolean oneShot)
1741 MediaPlaybackService.this.open(path, oneShot);
1743 public void open(int [] list, int position) {
1744 MediaPlaybackService.this.open(list, position);
1746 public int getQueuePosition() {
1747 return MediaPlaybackService.this.getQueuePosition();
1749 public void setQueuePosition(int index) {
1750 MediaPlaybackService.this.setQueuePosition(index);
1752 public boolean isPlaying() {
1753 return MediaPlaybackService.this.isPlaying();
1755 public void stop() {
1756 MediaPlaybackService.this.stop();
1758 public void pause() {
1759 MediaPlaybackService.this.pause();
1761 public void play() {
1762 MediaPlaybackService.this.play();
1764 public void prev() {
1765 MediaPlaybackService.this.prev();
1767 public void next() {
1768 MediaPlaybackService.this.next(true);
1770 public String getTrackName() {
1771 return MediaPlaybackService.this.getTrackName();
1773 public String getAlbumName() {
1774 return MediaPlaybackService.this.getAlbumName();
1776 public int getAlbumId() {
1777 return MediaPlaybackService.this.getAlbumId();
1779 public String getArtistName() {
1780 return MediaPlaybackService.this.getArtistName();
1782 public int getArtistId() {
1783 return MediaPlaybackService.this.getArtistId();
1785 public void enqueue(int [] list , int action) {
1786 MediaPlaybackService.this.enqueue(list, action);
1788 public int [] getQueue() {
1789 return MediaPlaybackService.this.getQueue();
1791 public void moveQueueItem(int from, int to) {
1792 MediaPlaybackService.this.moveQueueItem(from, to);
1794 public String getPath() {
1795 return MediaPlaybackService.this.getPath();
1797 public int getAudioId() {
1798 return MediaPlaybackService.this.getAudioId();
1800 public long position() {
1801 return MediaPlaybackService.this.position();
1803 public long duration() {
1804 return MediaPlaybackService.this.duration();
1806 public long seek(long pos) {
1807 return MediaPlaybackService.this.seek(pos);
1809 public void setShuffleMode(int shufflemode) {
1810 MediaPlaybackService.this.setShuffleMode(shufflemode);
1812 public int getShuffleMode() {
1813 return MediaPlaybackService.this.getShuffleMode();
1815 public int removeTracks(int first, int last) {
1816 return MediaPlaybackService.this.removeTracks(first, last);
1818 public int removeTrack(int id) {
1819 return MediaPlaybackService.this.removeTrack(id);
1821 public void setRepeatMode(int repeatmode) {
1822 MediaPlaybackService.this.setRepeatMode(repeatmode);
1824 public int getRepeatMode() {
1825 return MediaPlaybackService.this.getRepeatMode();
1827 public int getMediaMountedCount() {
1828 return MediaPlaybackService.this.getMediaMountedCount();