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.content.ContentResolver;
24 import android.content.ContentUris;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.BroadcastReceiver;
30 import android.content.SharedPreferences;
31 import android.content.SharedPreferences.Editor;
32 import android.database.Cursor;
33 import android.database.sqlite.SQLiteException;
34 import android.gadget.GadgetManager;
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.widget.RemoteViews;
49 import android.widget.Toast;
50 import com.android.internal.telephony.Phone;
51 import com.android.internal.telephony.PhoneStateIntentReceiver;
53 import java.io.IOException;
54 import java.util.Random;
55 import java.util.Vector;
58 * Provides "background" audio playback capabilities, allowing the
59 * user to switch between activities without stopping playback.
61 public 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
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;
71 public static final int SHUFFLE_NONE = 0;
72 public static final int SHUFFLE_NORMAL = 1;
73 public static final int SHUFFLE_AUTO = 2;
75 public static final int REPEAT_NONE = 0;
76 public static final int REPEAT_CURRENT = 1;
77 public static final int REPEAT_ALL = 2;
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";
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";
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";
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;
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
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;
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.
149 // interval after which we stop the service when idle
150 private static final int IDLE_DELAY = 60000;
152 private Handler mPhoneHandler = new Handler() {
154 public void handleMessage(Message msg) {
157 Phone.State state = mPsir.getPhoneState();
158 if (state == Phone.State.RINGING) {
159 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
160 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
161 if (ringvolume > 0) {
162 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
165 } else if (state == Phone.State.OFFHOOK) {
166 // pause the music while a conversation is in progress
167 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
169 } else if (state == Phone.State.IDLE) {
170 // start playing again
171 if (mResumeAfterCall) {
172 // resume playback only if music was playing
173 // when the call was answered
175 mResumeAfterCall = false;
185 private void startAndFadeIn() {
186 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
189 private Handler mMediaplayerHandler = new Handler() {
190 float mCurrentVolume = 1.0f;
192 public void handleMessage(Message msg) {
197 mPlayer.setVolume(mCurrentVolume);
199 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
201 mCurrentVolume += 0.01f;
202 if (mCurrentVolume < 1.0f) {
203 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
205 mCurrentVolume = 1.0f;
207 mPlayer.setVolume(mCurrentVolume);
214 // the server died when we were idle, so just
215 // reopen the same song (it will start again
216 // from the beginning though when the user
222 if (mRepeatMode == REPEAT_CURRENT) {
225 } else if (!mOneShot) {
228 notifyChange(PLAYBACK_COMPLETE);
229 MediaGadgetProvider.updateAllGadgets(MediaPlaybackService.this, true, null);
232 case RELEASE_WAKELOCK:
241 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
243 public void onReceive(Context context, Intent intent) {
244 String action = intent.getAction();
245 String cmd = intent.getStringExtra("command");
246 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
248 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
250 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
256 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
258 // TODO: add STOP action intent check
259 } else if (CMDSTOP.equals(cmd)) {
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 MediaGadgetProvider.updateAllGadgets(MediaPlaybackService.this,
272 public MediaPlaybackService() {
273 mPsir = new PhoneStateIntentReceiver(this, mPhoneHandler);
274 mPsir.notifyPhoneCallState(PHONE_CHANGED);
278 public void onCreate() {
281 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
282 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
284 registerExternalStorageListener();
286 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
287 mPlayer = new MultiPlayer();
288 mPlayer.setHandler(mMediaplayerHandler);
290 // Clear leftover notification in case this service previously got killed while playing
291 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
292 nm.cancel(PLAYBACKSERVICE_STATUS);
296 IntentFilter commandFilter = new IntentFilter();
297 commandFilter.addAction(SERVICECMD);
298 commandFilter.addAction(TOGGLEPAUSE_ACTION);
299 commandFilter.addAction(PAUSE_ACTION);
300 commandFilter.addAction(NEXT_ACTION);
301 commandFilter.addAction(PREVIOUS_ACTION);
302 registerReceiver(mIntentReceiver, commandFilter);
304 mPsir.registerIntent();
305 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
306 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
307 mWakeLock.setReferenceCounted(false);
309 // If the service was idle, but got killed before it stopped itself, the
310 // system will relaunch it. Make sure it gets stopped again in that case.
311 Message msg = mDelayedStopHandler.obtainMessage();
312 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
316 public void onDestroy() {
317 if (mCursor != null) {
322 unregisterReceiver(mIntentReceiver);
323 if (mUnmountReceiver != null) {
324 unregisterReceiver(mUnmountReceiver);
325 mUnmountReceiver = null;
327 mPsir.unregisterIntent();
332 private final char hexdigits [] = new char [] {
339 private void saveQueue(boolean full) {
343 Editor ed = mPreferences.edit();
344 //long start = System.currentTimeMillis();
346 StringBuilder q = new StringBuilder();
348 // The current playlist is saved as a list of "reverse hexadecimal"
349 // numbers, which we can generate faster than normal decimal or
350 // hexadecimal numbers, which in turn allows us to save the playlist
351 // more often without worrying too much about performance.
352 // (saving the full state takes about 40 ms under no-load conditions
354 int len = mPlayListLen;
355 for (int i = 0; i < len; i++) {
356 int n = mPlayList[i];
363 q.append(hexdigits[digit]);
368 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
369 ed.putString("queue", q.toString());
370 ed.putInt("cardid", mCardId);
372 ed.putInt("curpos", mPlayPos);
373 if (mPlayer.isInitialized()) {
374 ed.putLong("seekpos", mPlayer.position());
376 ed.putInt("repeatmode", mRepeatMode);
377 ed.putInt("shufflemode", mShuffleMode);
380 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
383 private void reloadQueue() {
386 boolean newstyle = false;
388 if (mPreferences.contains("cardid")) {
390 id = mPreferences.getInt("cardid", ~mCardId);
393 // Only restore the saved playlist if the card is still
394 // the same one as when the playlist was saved
395 q = mPreferences.getString("queue", "");
397 if (q != null && q.length() > 1) {
398 //Log.i("@@@@ service", "loaded queue: " + q);
399 String [] entries = q.split(";");
400 int len = entries.length;
401 ensurePlayListCapacity(len);
402 for (int i = 0; i < len; i++) {
404 String revhex = entries[i];
406 for (int j = revhex.length() - 1; j >= 0 ; j--) {
408 char c = revhex.charAt(j);
409 if (c >= '0' && c <= '9') {
411 } else if (c >= 'a' && c <= 'f') {
414 // bogus playlist data
421 mPlayList[i] = Integer.parseInt(entries[i]);
426 int pos = mPreferences.getInt("curpos", 0);
427 if (pos < 0 || pos >= len) {
428 // The saved playlist is bogus, discard it
434 // When reloadQueue is called in response to a card-insertion,
435 // we might not be able to query the media provider right away.
436 // To deal with this, try querying for the current file, and if
437 // that fails, wait a while and try again. If that too fails,
438 // assume there is a problem and don't restore the state.
439 Cursor c = MusicUtils.query(this,
440 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
441 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
442 if (c == null || c.getCount() == 0) {
443 // wait a bit and try again
444 SystemClock.sleep(3000);
445 c = getContentResolver().query(
446 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
447 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
453 // Make sure we don't auto-skip to the next song, since that
454 // also starts playback. What could happen in that case is:
456 // - go to UMS and delete some files, including the currently playing one
457 // - come back from UMS
459 // - music app is killed for some reason (out of memory)
460 // - music service is restarted, service restores state, doesn't find
461 // the "current" file, goes to the next and: playback starts on its
462 // own, potentially at some random inconvenient time.
463 mOpenFailedCounter = 20;
467 if (!mPlayer.isInitialized()) {
468 // couldn't restore the saved state
473 long seekpos = mPreferences.getLong("seekpos", 0);
474 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
476 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
477 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
478 repmode = REPEAT_NONE;
480 mRepeatMode = repmode;
482 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
483 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
484 shufmode = SHUFFLE_NONE;
486 if (shufmode == SHUFFLE_AUTO) {
487 if (! makeAutoShuffleList()) {
488 shufmode = SHUFFLE_NONE;
491 mShuffleMode = shufmode;
496 public IBinder onBind(Intent intent) {
497 mDelayedStopHandler.removeCallbacksAndMessages(null);
498 mServiceInUse = true;
503 public void onRebind(Intent intent) {
504 mDelayedStopHandler.removeCallbacksAndMessages(null);
505 mServiceInUse = true;
509 public void onStart(Intent intent, int startId) {
510 mServiceStartId = startId;
511 mDelayedStopHandler.removeCallbacksAndMessages(null);
513 String action = intent.getAction();
514 String cmd = intent.getStringExtra("command");
516 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
518 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
520 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
526 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
528 } else if (CMDSTOP.equals(cmd)) {
533 // make sure the service will shut down on its own if it was
534 // just started but not bound to and nothing is playing
535 mDelayedStopHandler.removeCallbacksAndMessages(null);
536 Message msg = mDelayedStopHandler.obtainMessage();
537 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
541 public boolean onUnbind(Intent intent) {
542 mServiceInUse = false;
544 // Take a snapshot of the current playlist
547 if (isPlaying() || mResumeAfterCall) {
548 // something is currently playing, or will be playing once
549 // an in-progress call ends, so don't stop the service now.
553 // If there is a playlist but playback is paused, then wait a while
554 // before stopping the service, so that pause/resume isn't slow.
555 // Also delay stopping the service if we're transitioning between tracks.
556 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
557 Message msg = mDelayedStopHandler.obtainMessage();
558 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
562 // No active playlist, OK to stop the service right now
563 stopSelf(mServiceStartId);
567 private Handler mDelayedStopHandler = new Handler() {
569 public void handleMessage(Message msg) {
570 // Check again to make sure nothing is playing right now
571 if (isPlaying() || mResumeAfterCall || mServiceInUse
572 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
575 // save the queue again, because it might have changed
576 // since the user exited the music app (because of
577 // party-shuffle or because the play-position changed)
579 stopSelf(mServiceStartId);
584 * Called when we receive a ACTION_MEDIA_EJECT notification.
586 * @param storagePath path to mount point for the removed media
588 public void closeExternalStorageFiles(String storagePath) {
589 // stop playback and clean up if the SD card is going to be unmounted.
591 notifyChange(QUEUE_CHANGED);
592 notifyChange(META_CHANGED);
593 MediaGadgetProvider.updateAllGadgets(this, true, null);
597 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
598 * The intent will call closeExternalStorageFiles() if the external media
599 * is going to be ejected, so applications can clean up any files they have open.
601 public void registerExternalStorageListener() {
602 if (mUnmountReceiver == null) {
603 mUnmountReceiver = new BroadcastReceiver() {
605 public void onReceive(Context context, Intent intent) {
606 String action = intent.getAction();
607 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
609 mOneShot = true; // This makes us not save the state again later,
610 // which would be wrong because the song ids and
611 // card id might not match.
612 closeExternalStorageFiles(intent.getData().getPath());
613 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
614 mMediaMountedCount++;
615 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
617 notifyChange(QUEUE_CHANGED);
618 notifyChange(META_CHANGED);
619 MediaGadgetProvider.updateAllGadgets(MediaPlaybackService.this, true, null);
623 IntentFilter iFilter = new IntentFilter();
624 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
625 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
626 iFilter.addDataScheme("file");
627 registerReceiver(mUnmountReceiver, iFilter);
632 * Notify the change-receivers that something has changed.
633 * The intent that is sent contains the following data
634 * for the currently playing track:
635 * "id" - Integer: the database row ID
636 * "artist" - String: the name of the artist
637 * "album" - String: the name of the album
638 * "track" - String: the name of the track
639 * The intent has an action that is one of
640 * "com.android.music.metachanged"
641 * "com.android.music.queuechanged",
642 * "com.android.music.playbackcomplete"
643 * "com.android.music.playstatechanged"
644 * respectively indicating that a new track has
645 * started playing, that the playback queue has
646 * changed, that playback has stopped because
647 * the last file in the list has been played,
648 * or that the play-state changed (paused/resumed).
650 private void notifyChange(String what) {
652 Intent i = new Intent(what);
653 i.putExtra("id", Integer.valueOf(getAudioId()));
654 i.putExtra("artist", getArtistName());
655 i.putExtra("album",getAlbumName());
656 i.putExtra("track", getTrackName());
659 if (what.equals(QUEUE_CHANGED)) {
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
671 int [] newlist = new int[size * 2];
672 int len = mPlayListLen;
673 for (int i = 0; i < len; i++) {
674 newlist[i] = mPlayList[i];
678 // FIXME: shrink the array when the needed size is much smaller
679 // than the allocated size
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
689 ensurePlayListCapacity(mPlayListLen + addlen);
690 if (position > mPlayListLen) {
691 position = mPlayListLen;
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];
700 // copy list into playlist
701 for (int i = 0; i < addlen; i++) {
702 mPlayList[position + i] = list[i];
704 mPlayListLen += addlen;
708 * Appends a list of tracks to the current playlist.
709 * If nothing is playing currently, playback will be started at
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
716 public void enqueue(int [] list, int action) {
718 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
719 addToPlayList(list, mPlayPos + 1);
720 notifyChange(QUEUE_CHANGED);
722 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
723 addToPlayList(list, Integer.MAX_VALUE);
724 notifyChange(QUEUE_CHANGED);
726 mPlayPos = mPlayListLen - list.length;
729 notifyChange(META_CHANGED);
730 MediaGadgetProvider.updateAllGadgets(this, true, null);
738 notifyChange(META_CHANGED);
739 MediaGadgetProvider.updateAllGadgets(this, true, null);
745 * Replaces the current playlist with a new list,
746 * and prepares for starting playback at the specified
747 * position in the list, or a random position if the
748 * specified position is 0.
749 * @param list The new list of tracks.
751 public void open(int [] list, int position) {
752 synchronized (this) {
753 if (mShuffleMode == SHUFFLE_AUTO) {
754 mShuffleMode = SHUFFLE_NORMAL;
756 int listlength = list.length;
757 boolean newlist = true;
758 if (mPlayListLen == listlength) {
759 // possible fast path: list might be the same
761 for (int i = 0; i < listlength; i++) {
762 if (list[i] != mPlayList[i]) {
769 addToPlayList(list, -1);
770 notifyChange(QUEUE_CHANGED);
772 int oldpos = mPlayPos;
776 mPlayPos = mRand.nextInt(mPlayListLen);
780 saveBookmarkIfNeeded();
782 if (!newlist && mPlayPos != oldpos) {
783 // the queue didn't change, but the position did
784 notifyChange(META_CHANGED);
790 * Moves the item at index1 to index2.
794 public void moveQueueItem(int index1, int index2) {
795 synchronized (this) {
796 if (index1 >= mPlayListLen) {
797 index1 = mPlayListLen - 1;
799 if (index2 >= mPlayListLen) {
800 index2 = mPlayListLen - 1;
802 if (index1 < index2) {
803 int tmp = mPlayList[index1];
804 for (int i = index1; i < index2; i++) {
805 mPlayList[i] = mPlayList[i+1];
807 mPlayList[index2] = tmp;
808 if (mPlayPos == index1) {
810 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
813 } else if (index2 < index1) {
814 int tmp = mPlayList[index1];
815 for (int i = index1; i > index2; i--) {
816 mPlayList[i] = mPlayList[i-1];
818 mPlayList[index2] = tmp;
819 if (mPlayPos == index1) {
821 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
825 notifyChange(QUEUE_CHANGED);
830 * Returns the current play list
831 * @return An array of integers containing the IDs of the tracks in the play list
833 public int [] getQueue() {
834 synchronized (this) {
835 int len = mPlayListLen;
836 int [] list = new int[len];
837 for (int i = 0; i < len; i++) {
838 list[i] = mPlayList[i];
844 private void openCurrent() {
845 synchronized (this) {
846 if (mCursor != null) {
850 if (mPlayListLen == 0) {
855 String id = String.valueOf(mPlayList[mPlayPos]);
857 mCursor = getContentResolver().query(
858 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
859 mCursorCols, "_id=" + id , null, null);
860 if (mCursor != null) {
861 mCursor.moveToFirst();
862 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
863 // go to bookmark if needed
865 long bookmark = getBookmark();
866 // Start playing a little bit before the bookmark,
867 // so it's easier to get back in to the narrative.
868 seek(bookmark - 5000);
874 public void openAsync(String path) {
875 synchronized (this) {
880 mRepeatMode = REPEAT_NONE;
881 ensurePlayListCapacity(1);
887 mPlayer.setDataSourceAsync(mFileToPlay);
893 * Opens the specified file and readies it for playback.
895 * @param path The full path of the file to be opened.
896 * @param oneshot when set to true, playback will stop after this file completes, instead
897 * of moving on to the next track in the list
899 public void open(String path, boolean oneshot) {
900 synchronized (this) {
906 mRepeatMode = REPEAT_NONE;
907 ensurePlayListCapacity(1);
912 // if mCursor is null, try to associate path with a database cursor
913 if (mCursor == null) {
915 ContentResolver resolver = getContentResolver();
918 String selectionArgs[];
919 if (path.startsWith("content://media/")) {
920 uri = Uri.parse(path);
922 selectionArgs = null;
924 uri = MediaStore.Audio.Media.getContentUriForPath(path);
925 where = MediaStore.Audio.Media.DATA + "=?";
926 selectionArgs = new String[] { path };
930 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
931 if (mCursor != null) {
932 if (mCursor.getCount() == 0) {
936 mCursor.moveToNext();
937 ensurePlayListCapacity(1);
939 mPlayList[0] = mCursor.getInt(IDCOLIDX);
943 } catch (UnsupportedOperationException ex) {
947 mPlayer.setDataSource(mFileToPlay);
949 if (! mPlayer.isInitialized()) {
951 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
952 // beware: this ends up being recursive because next() calls open() again.
955 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
956 // need to make sure we only shows this once
957 mOpenFailedCounter = 0;
959 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
963 mOpenFailedCounter = 0;
969 * Starts playback of a previously opened file.
972 if (mPlayer.isInitialized()) {
977 NotificationManager nm = (NotificationManager)
978 getSystemService(Context.NOTIFICATION_SERVICE);
980 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
981 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
982 if (getAudioId() < 0) {
984 views.setTextViewText(R.id.trackname, getPath());
985 views.setTextViewText(R.id.artistalbum, null);
987 String artist = getArtistName();
988 views.setTextViewText(R.id.trackname, getTrackName());
989 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
990 artist = getString(R.string.unknown_artist_name);
992 String album = getAlbumName();
993 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
994 album = getString(R.string.unknown_album_name);
997 views.setTextViewText(R.id.artistalbum,
998 getString(R.string.notification_artist_album, artist, album)
1002 Intent statusintent = new Intent("com.android.music.PLAYBACK_VIEWER");
1003 statusintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1004 Notification status = new Notification();
1005 status.contentView = views;
1006 status.flags |= Notification.FLAG_ONGOING_EVENT;
1007 status.icon = R.drawable.stat_notify_musicplayer;
1008 status.contentIntent = PendingIntent.getActivity(this, 0,
1009 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1010 nm.notify(PLAYBACKSERVICE_STATUS, status);
1011 notifyChange(PLAYSTATE_CHANGED);
1012 MediaGadgetProvider.updateAllGadgets(this, false, null);
1016 private void stop(boolean remove_status_icon) {
1017 if (mPlayer.isInitialized()) {
1021 if (mCursor != null) {
1025 if (remove_status_icon) {
1028 setForeground(false);
1029 mWasPlaying = false;
1035 public void stop() {
1040 * Pauses playback (call play() to resume)
1042 public void pause() {
1046 setForeground(false);
1047 mWasPlaying = false;
1048 notifyChange(PLAYSTATE_CHANGED);
1049 saveBookmarkIfNeeded();
1050 MediaGadgetProvider.updateAllGadgets(this, false, null);
1054 /** Returns whether playback is currently paused
1056 * @return true if playback is paused, false if not
1058 public boolean isPlaying() {
1059 if (mPlayer.isInitialized()) {
1060 return mPlayer.isPlaying();
1066 Desired behavior for prev/next/shuffle:
1068 - NEXT will move to the next track in the list when not shuffling, and to
1069 a track randomly picked from the not-yet-played tracks when shuffling.
1070 If all tracks have already been played, pick from the full set, but
1071 avoid picking the previously played track if possible.
1072 - when shuffling, PREV will go to the previously played track. Hitting PREV
1073 again will go to the track played before that, etc. When the start of the
1074 history has been reached, PREV is a no-op.
1075 When not shuffling, PREV will go to the sequentially previous track (the
1076 difference with the shuffle-case is mainly that when not shuffling, the
1077 user can back up to tracks that are not in the history).
1080 When playing an album with 10 tracks from the start, and enabling shuffle
1081 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1082 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1083 When hitting 'prev' 8 times while playing track 7 in this example, the
1084 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1085 a random track will be picked again. If at any time user disables shuffling
1086 the next/previous track will be picked in sequential order again.
1089 public void prev() {
1090 synchronized (this) {
1092 // we were playing a specific file not part of a playlist, so there is no 'previous'
1097 if (mShuffleMode == SHUFFLE_NORMAL) {
1098 // go to previously-played track and remove it from the history
1099 int histsize = mHistory.size();
1100 if (histsize == 0) {
1104 Integer pos = mHistory.remove(histsize - 1);
1105 mPlayPos = pos.intValue();
1110 mPlayPos = mPlayListLen - 1;
1113 saveBookmarkIfNeeded();
1117 notifyChange(META_CHANGED);
1118 MediaGadgetProvider.updateAllGadgets(this, true, null);
1122 public void next(boolean force) {
1123 synchronized (this) {
1125 // we were playing a specific file not part of a playlist, so there is no 'next'
1131 // Store the current file in the history, but keep the history at a
1133 if (mPlayPos >= 0) {
1134 mHistory.add(Integer.valueOf(mPlayPos));
1136 if (mHistory.size() > MAX_HISTORY_SIZE) {
1137 mHistory.removeElementAt(0);
1140 if (mShuffleMode == SHUFFLE_NORMAL) {
1141 // Pick random next track from the not-yet-played ones
1142 // TODO: make it work right after adding/removing items in the queue.
1144 int numTracks = mPlayListLen;
1145 int[] tracks = new int[numTracks];
1146 for (int i=0;i < numTracks; i++) {
1150 int numHistory = mHistory.size();
1151 int numUnplayed = numTracks;
1152 for (int i=0;i < numHistory; i++) {
1153 int idx = mHistory.get(i).intValue();
1154 if (idx < numTracks && tracks[idx] >= 0) {
1160 // 'numUnplayed' now indicates how many tracks have not yet
1161 // been played, and 'tracks' contains the indices of those
1163 if (numUnplayed <=0) {
1164 // everything's already been played
1165 if (mRepeatMode == REPEAT_ALL || force) {
1166 //pick from full set
1167 numUnplayed = numTracks;
1168 for (int i=0;i < numTracks; i++) {
1177 int skip = mRand.nextInt(numUnplayed);
1180 while (tracks[++cnt] < 0)
1188 } else if (mShuffleMode == SHUFFLE_AUTO) {
1189 doAutoShuffleUpdate();
1192 if (mPlayPos >= mPlayListLen - 1) {
1193 // we're at the end of the list
1194 if (mRepeatMode == REPEAT_NONE && !force) {
1197 notifyChange(PLAYBACK_COMPLETE);
1198 MediaGadgetProvider.updateAllGadgets(this, true, null);
1200 } else if (mRepeatMode == REPEAT_ALL || force) {
1207 saveBookmarkIfNeeded();
1211 notifyChange(META_CHANGED);
1212 MediaGadgetProvider.updateAllGadgets(this, true, null);
1216 private void gotoIdleState() {
1217 NotificationManager nm =
1218 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1219 nm.cancel(PLAYBACKSERVICE_STATUS);
1220 mDelayedStopHandler.removeCallbacksAndMessages(null);
1221 Message msg = mDelayedStopHandler.obtainMessage();
1222 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1225 private void saveBookmarkIfNeeded() {
1228 long pos = position();
1229 long bookmark = getBookmark();
1230 long duration = duration();
1231 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1232 (pos > bookmark && (pos - 10000) < bookmark)) {
1233 // The existing bookmark is close to the current
1234 // position, so don't update it.
1237 if (pos < 15000 || (pos + 10000) > duration) {
1238 // if we're near the start or end, clear the bookmark
1242 // write 'pos' to the bookmark field
1243 ContentValues values = new ContentValues();
1244 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1245 Uri uri = ContentUris.withAppendedId(
1246 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1247 getContentResolver().update(uri, values, null, null);
1249 } catch (SQLiteException ex) {
1253 // Make sure there are at least 5 items after the currently playing item
1254 // and no more than 10 items before.
1255 private void doAutoShuffleUpdate() {
1256 boolean notify = false;
1257 // remove old entries
1258 if (mPlayPos > 10) {
1259 removeTracks(0, mPlayPos - 9);
1262 // add new entries if needed
1263 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1264 for (int i = 0; i < to_add; i++) {
1265 // pick something at random from the list
1266 int idx = mRand.nextInt(mAutoShuffleList.length);
1267 Integer which = mAutoShuffleList[idx];
1268 ensurePlayListCapacity(mPlayListLen + 1);
1269 mPlayList[mPlayListLen++] = which;
1273 notifyChange(QUEUE_CHANGED);
1277 // A simple variation of Random that makes sure that the
1278 // value it returns is not equal to the value it returned
1279 // previously, unless the interval is 1.
1280 private class Shuffler {
1281 private int mPrevious;
1282 private Random mRandom = new Random();
1283 public int nextInt(int interval) {
1286 ret = mRandom.nextInt(interval);
1287 } while (ret == mPrevious && interval > 1);
1293 private boolean makeAutoShuffleList() {
1294 ContentResolver res = getContentResolver();
1297 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1298 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1300 if (c == null || c.getCount() == 0) {
1303 int len = c.getCount();
1304 int[] list = new int[len];
1305 for (int i = 0; i < len; i++) {
1307 list[i] = c.getInt(0);
1309 mAutoShuffleList = list;
1311 } catch (RuntimeException ex) {
1321 * Removes the range of tracks specified from the play list. If a file within the range is
1322 * the file currently being played, playback will move to the next file after the
1324 * @param first The first file to be removed
1325 * @param last The last file to be removed
1326 * @return the number of tracks deleted
1328 public int removeTracks(int first, int last) {
1329 int numremoved = removeTracksInternal(first, last);
1330 if (numremoved > 0) {
1331 notifyChange(QUEUE_CHANGED);
1336 private int removeTracksInternal(int first, int last) {
1337 synchronized (this) {
1338 if (last < first) return 0;
1339 if (first < 0) first = 0;
1340 if (last >= mPlayListLen) last = mPlayListLen - 1;
1342 boolean gotonext = false;
1343 if (first <= mPlayPos && mPlayPos <= last) {
1346 } else if (mPlayPos > last) {
1347 mPlayPos -= (last - first + 1);
1349 int num = mPlayListLen - last - 1;
1350 for (int i = 0; i < num; i++) {
1351 mPlayList[first + i] = mPlayList[last + 1 + i];
1353 mPlayListLen -= last - first + 1;
1356 if (mPlayListLen == 0) {
1360 if (mPlayPos >= mPlayListLen) {
1363 boolean wasPlaying = isPlaying();
1371 return last - first + 1;
1376 * Removes all instances of the track with the given id
1377 * from the playlist.
1378 * @param id The id to be removed
1379 * @return how many instances of the track were removed
1381 public int removeTrack(int id) {
1383 synchronized (this) {
1384 for (int i = 0; i < mPlayListLen; i++) {
1385 if (mPlayList[i] == id) {
1386 numremoved += removeTracksInternal(i, i);
1391 if (numremoved > 0) {
1392 notifyChange(QUEUE_CHANGED);
1397 public void setShuffleMode(int shufflemode) {
1398 synchronized(this) {
1399 if (mShuffleMode == shufflemode) {
1402 mShuffleMode = shufflemode;
1403 if (mShuffleMode == SHUFFLE_AUTO) {
1404 if (makeAutoShuffleList()) {
1406 doAutoShuffleUpdate();
1410 notifyChange(META_CHANGED);
1411 MediaGadgetProvider.updateAllGadgets(this, true, null);
1414 // failed to build a list of files to shuffle
1415 mShuffleMode = SHUFFLE_NONE;
1421 public int getShuffleMode() {
1422 return mShuffleMode;
1425 public void setRepeatMode(int repeatmode) {
1426 synchronized(this) {
1427 mRepeatMode = repeatmode;
1431 public int getRepeatMode() {
1435 public int getMediaMountedCount() {
1436 return mMediaMountedCount;
1440 * Returns the path of the currently playing file, or null if
1441 * no file is currently playing.
1443 public String getPath() {
1448 * Returns the rowid of the currently playing file, or -1 if
1449 * no file is currently playing.
1451 public int getAudioId() {
1452 synchronized (this) {
1453 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1454 return mPlayList[mPlayPos];
1461 * Returns the position in the queue
1462 * @return the position in the queue
1464 public int getQueuePosition() {
1465 synchronized(this) {
1471 * Starts playing the track at the given position in the queue.
1472 * @param pos The position in the queue of the track that will be played.
1474 public void setQueuePosition(int pos) {
1475 synchronized(this) {
1480 notifyChange(META_CHANGED);
1481 MediaGadgetProvider.updateAllGadgets(this, true, null);
1485 public String getArtistName() {
1486 synchronized(this) {
1487 if (mCursor == null) {
1490 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1494 public int getArtistId() {
1495 synchronized (this) {
1496 if (mCursor == null) {
1499 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1503 public String getAlbumName() {
1504 synchronized (this) {
1505 if (mCursor == null) {
1508 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1512 public int getAlbumId() {
1513 synchronized (this) {
1514 if (mCursor == null) {
1517 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1521 public String getTrackName() {
1522 synchronized (this) {
1523 if (mCursor == null) {
1526 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1530 private boolean isPodcast() {
1531 synchronized (this) {
1532 if (mCursor == null) {
1535 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1539 private long getBookmark() {
1540 synchronized (this) {
1541 if (mCursor == null) {
1544 return mCursor.getLong(BOOKMARKCOLIDX);
1549 * Returns the duration of the file in milliseconds.
1550 * Currently this method returns -1 for the duration of MIDI files.
1552 public long duration() {
1553 if (mPlayer.isInitialized()) {
1554 return mPlayer.duration();
1560 * Returns the current playback position in milliseconds
1562 public long position() {
1563 if (mPlayer.isInitialized()) {
1564 return mPlayer.position();
1570 * Seeks to the position specified.
1572 * @param pos The position to seek to, in milliseconds
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);
1584 * Provides a unified interface for dealing with midi files and
1585 * other media files.
1587 private class MultiPlayer {
1588 private MediaPlayer mMediaPlayer = new MediaPlayer();
1589 private Handler mHandler;
1590 private boolean mIsInitialized = false;
1592 public MultiPlayer() {
1593 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1596 public void setDataSourceAsync(String path) {
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;
1607 } catch (IllegalArgumentException ex) {
1608 // TODO: notify the user why the file couldn't be opened
1609 mIsInitialized = false;
1612 mMediaPlayer.setOnCompletionListener(listener);
1613 mMediaPlayer.setOnErrorListener(errorListener);
1615 mIsInitialized = true;
1618 public void setDataSource(String path) {
1620 mMediaPlayer.reset();
1621 mMediaPlayer.setOnPreparedListener(null);
1622 if (path.startsWith("content://")) {
1623 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1625 mMediaPlayer.setDataSource(path);
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;
1633 } catch (IllegalArgumentException ex) {
1634 // TODO: notify the user why the file couldn't be opened
1635 mIsInitialized = false;
1638 mMediaPlayer.setOnCompletionListener(listener);
1639 mMediaPlayer.setOnErrorListener(errorListener);
1641 mIsInitialized = true;
1644 public boolean isInitialized() {
1645 return mIsInitialized;
1648 public void start() {
1649 mMediaPlayer.start();
1652 public void stop() {
1653 mMediaPlayer.reset();
1654 mIsInitialized = false;
1657 public void pause() {
1658 mMediaPlayer.pause();
1661 public boolean isPlaying() {
1662 return mMediaPlayer.isPlaying();
1665 public void setHandler(Handler handler) {
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);
1682 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1683 public void onPrepared(MediaPlayer mp) {
1684 notifyChange(ASYNC_OPEN_COMPLETE);
1688 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1689 public boolean onError(MediaPlayer mp, int what, int extra) {
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);
1708 public long duration() {
1709 return mMediaPlayer.getDuration();
1712 public long position() {
1713 return mMediaPlayer.getCurrentPosition();
1716 public long seek(long whereto) {
1717 mMediaPlayer.seekTo((int) whereto);
1721 public void setVolume(float vol) {
1722 mMediaPlayer.setVolume(vol, vol);
1726 private final IMediaPlaybackService.Stub mBinder = new IMediaPlaybackService.Stub()
1728 public void openfileAsync(String path)
1730 MediaPlaybackService.this.openAsync(path);
1732 public void openfile(String path)
1734 MediaPlaybackService.this.open(path, true);
1736 public void open(int [] list, int position) {
1737 MediaPlaybackService.this.open(list, position);
1739 public int getQueuePosition() {
1740 return MediaPlaybackService.this.getQueuePosition();
1742 public void setQueuePosition(int index) {
1743 MediaPlaybackService.this.setQueuePosition(index);
1745 public boolean isPlaying() {
1746 return MediaPlaybackService.this.isPlaying();
1748 public void stop() {
1749 MediaPlaybackService.this.stop();
1751 public void pause() {
1752 MediaPlaybackService.this.pause();
1754 public void play() {
1755 MediaPlaybackService.this.play();
1757 public void prev() {
1758 MediaPlaybackService.this.prev();
1760 public void next() {
1761 MediaPlaybackService.this.next(true);
1763 public String getTrackName() {
1764 return MediaPlaybackService.this.getTrackName();
1766 public String getAlbumName() {
1767 return MediaPlaybackService.this.getAlbumName();
1769 public int getAlbumId() {
1770 return MediaPlaybackService.this.getAlbumId();
1772 public String getArtistName() {
1773 return MediaPlaybackService.this.getArtistName();
1775 public int getArtistId() {
1776 return MediaPlaybackService.this.getArtistId();
1778 public void enqueue(int [] list , int action) {
1779 MediaPlaybackService.this.enqueue(list, action);
1781 public int [] getQueue() {
1782 return MediaPlaybackService.this.getQueue();
1784 public void moveQueueItem(int from, int to) {
1785 MediaPlaybackService.this.moveQueueItem(from, to);
1787 public String getPath() {
1788 return MediaPlaybackService.this.getPath();
1790 public int getAudioId() {
1791 return MediaPlaybackService.this.getAudioId();
1793 public long position() {
1794 return MediaPlaybackService.this.position();
1796 public long duration() {
1797 return MediaPlaybackService.this.duration();
1799 public long seek(long pos) {
1800 return MediaPlaybackService.this.seek(pos);
1802 public void setShuffleMode(int shufflemode) {
1803 MediaPlaybackService.this.setShuffleMode(shufflemode);
1805 public int getShuffleMode() {
1806 return MediaPlaybackService.this.getShuffleMode();
1808 public int removeTracks(int first, int last) {
1809 return MediaPlaybackService.this.removeTracks(first, last);
1811 public int removeTrack(int id) {
1812 return MediaPlaybackService.this.removeTrack(id);
1814 public void setRepeatMode(int repeatmode) {
1815 MediaPlaybackService.this.setRepeatMode(repeatmode);
1817 public int getRepeatMode() {
1818 return MediaPlaybackService.this.getRepeatMode();
1820 public int getMediaMountedCount() {
1821 return MediaPlaybackService.this.getMediaMountedCount();