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.telephony.PhoneStateListener;
49 import android.telephony.TelephonyManager;
50 import android.util.Log;
51 import android.widget.RemoteViews;
52 import android.widget.Toast;
54 import java.io.IOException;
55 import java.lang.ref.WeakReference;
56 import java.util.Random;
57 import java.util.Vector;
60 * Provides "background" audio playback capabilities, allowing the
61 * user to switch between activities without stopping playback.
63 public class MediaPlaybackService extends Service {
64 /** used to specify whether enqueue() should start playing
65 * the new list of files right away, next or once all the currently
66 * queued files have been played
68 public static final int NOW = 1;
69 public static final int NEXT = 2;
70 public static final int LAST = 3;
71 public static final int PLAYBACKSERVICE_STATUS = 1;
73 public static final int SHUFFLE_NONE = 0;
74 public static final int SHUFFLE_NORMAL = 1;
75 public static final int SHUFFLE_AUTO = 2;
77 public static final int REPEAT_NONE = 0;
78 public static final int REPEAT_CURRENT = 1;
79 public static final int REPEAT_ALL = 2;
81 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
82 public static final String META_CHANGED = "com.android.music.metachanged";
83 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
84 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
85 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
87 public static final String SERVICECMD = "com.android.music.musicservicecommand";
88 public static final String CMDNAME = "command";
89 public static final String CMDTOGGLEPAUSE = "togglepause";
90 public static final String CMDSTOP = "stop";
91 public static final String CMDPAUSE = "pause";
92 public static final String CMDPREVIOUS = "previous";
93 public static final String CMDNEXT = "next";
95 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
96 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
97 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
98 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
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 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 mIsSupposedToBePlaying = 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 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
151 // interval after which we stop the service when idle
152 private static final int IDLE_DELAY = 60000;
154 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
156 public void onCallStateChanged(int state, String incomingNumber) {
157 if (state == TelephonyManager.CALL_STATE_RINGING) {
158 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
159 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
160 if (ringvolume > 0) {
161 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
164 } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
165 // pause the music while a conversation is in progress
166 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
168 } else if (state == TelephonyManager.CALL_STATE_IDLE) {
169 // start playing again
170 if (mResumeAfterCall) {
171 // resume playback only if music was playing
172 // when the call was answered
174 mResumeAfterCall = false;
180 private void startAndFadeIn() {
181 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
184 private Handler mMediaplayerHandler = new Handler() {
185 float mCurrentVolume = 1.0f;
187 public void handleMessage(Message msg) {
192 mPlayer.setVolume(mCurrentVolume);
194 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
196 mCurrentVolume += 0.01f;
197 if (mCurrentVolume < 1.0f) {
198 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
200 mCurrentVolume = 1.0f;
202 mPlayer.setVolume(mCurrentVolume);
206 if (mIsSupposedToBePlaying) {
209 // the server died when we were idle, so just
210 // reopen the same song (it will start again
211 // from the beginning though when the user
217 if (mRepeatMode == REPEAT_CURRENT) {
220 } else if (!mOneShot) {
223 notifyChange(PLAYBACK_COMPLETE);
224 mIsSupposedToBePlaying = false;
227 case RELEASE_WAKELOCK:
236 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
238 public void onReceive(Context context, Intent intent) {
239 String action = intent.getAction();
240 String cmd = intent.getStringExtra("command");
241 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
243 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
245 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
251 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
253 } else if (CMDSTOP.equals(cmd)) {
256 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
257 // Someone asked us to refresh a set of specific widgets, probably
258 // because they were just added.
259 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
260 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
265 public MediaPlaybackService() {
269 public void onCreate() {
272 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
273 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
275 registerExternalStorageListener();
277 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
278 mPlayer = new MultiPlayer();
279 mPlayer.setHandler(mMediaplayerHandler);
281 // Clear leftover notification in case this service previously got killed while playing
282 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
283 nm.cancel(PLAYBACKSERVICE_STATUS);
287 IntentFilter commandFilter = new IntentFilter();
288 commandFilter.addAction(SERVICECMD);
289 commandFilter.addAction(TOGGLEPAUSE_ACTION);
290 commandFilter.addAction(PAUSE_ACTION);
291 commandFilter.addAction(NEXT_ACTION);
292 commandFilter.addAction(PREVIOUS_ACTION);
293 registerReceiver(mIntentReceiver, commandFilter);
295 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
296 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
297 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
298 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
299 mWakeLock.setReferenceCounted(false);
301 // If the service was idle, but got killed before it stopped itself, the
302 // system will relaunch it. Make sure it gets stopped again in that case.
303 Message msg = mDelayedStopHandler.obtainMessage();
304 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
308 public void onDestroy() {
309 // Check that we're not being destroyed while something is still playing.
311 Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
313 // release all MediaPlayer resources, including the native player and wakelocks
317 // make sure there aren't any other messages coming
318 mDelayedStopHandler.removeCallbacksAndMessages(null);
319 mMediaplayerHandler.removeCallbacksAndMessages(null);
321 if (mCursor != null) {
326 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
327 tmgr.listen(mPhoneStateListener, 0);
329 unregisterReceiver(mIntentReceiver);
330 if (mUnmountReceiver != null) {
331 unregisterReceiver(mUnmountReceiver);
332 mUnmountReceiver = null;
338 private final char hexdigits [] = new char [] {
345 private void saveQueue(boolean full) {
349 Editor ed = mPreferences.edit();
350 //long start = System.currentTimeMillis();
352 StringBuilder q = new StringBuilder();
354 // The current playlist is saved as a list of "reverse hexadecimal"
355 // numbers, which we can generate faster than normal decimal or
356 // hexadecimal numbers, which in turn allows us to save the playlist
357 // more often without worrying too much about performance.
358 // (saving the full state takes about 40 ms under no-load conditions
360 int len = mPlayListLen;
361 for (int i = 0; i < len; i++) {
362 int n = mPlayList[i];
369 q.append(hexdigits[digit]);
374 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
375 ed.putString("queue", q.toString());
376 ed.putInt("cardid", mCardId);
378 ed.putInt("curpos", mPlayPos);
379 if (mPlayer.isInitialized()) {
380 ed.putLong("seekpos", mPlayer.position());
382 ed.putInt("repeatmode", mRepeatMode);
383 ed.putInt("shufflemode", mShuffleMode);
386 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
389 private void reloadQueue() {
392 boolean newstyle = false;
394 if (mPreferences.contains("cardid")) {
396 id = mPreferences.getInt("cardid", ~mCardId);
399 // Only restore the saved playlist if the card is still
400 // the same one as when the playlist was saved
401 q = mPreferences.getString("queue", "");
403 int qlen = q != null ? q.length() : 0;
405 //Log.i("@@@@ service", "loaded queue: " + q);
409 for (int i = 0; i < qlen; i++) {
410 char c = q.charAt(i);
412 ensurePlayListCapacity(plen + 1);
418 if (c >= '0' && c <= '9') {
419 n += ((c - '0') << shift);
420 } else if (c >= 'a' && c <= 'f') {
421 n += ((10 + c - 'a') << shift);
423 // bogus playlist data
432 int pos = mPreferences.getInt("curpos", 0);
433 if (pos < 0 || pos >= mPlayListLen) {
434 // The saved playlist is bogus, discard it
440 // When reloadQueue is called in response to a card-insertion,
441 // we might not be able to query the media provider right away.
442 // To deal with this, try querying for the current file, and if
443 // that fails, wait a while and try again. If that too fails,
444 // assume there is a problem and don't restore the state.
445 Cursor c = MusicUtils.query(this,
446 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
447 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
448 if (c == null || c.getCount() == 0) {
449 // wait a bit and try again
450 SystemClock.sleep(3000);
451 c = getContentResolver().query(
452 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
453 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
459 // Make sure we don't auto-skip to the next song, since that
460 // also starts playback. What could happen in that case is:
462 // - go to UMS and delete some files, including the currently playing one
463 // - come back from UMS
465 // - music app is killed for some reason (out of memory)
466 // - music service is restarted, service restores state, doesn't find
467 // the "current" file, goes to the next and: playback starts on its
468 // own, potentially at some random inconvenient time.
469 mOpenFailedCounter = 20;
473 if (!mPlayer.isInitialized()) {
474 // couldn't restore the saved state
479 long seekpos = mPreferences.getLong("seekpos", 0);
480 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
482 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
483 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
484 repmode = REPEAT_NONE;
486 mRepeatMode = repmode;
488 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
489 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
490 shufmode = SHUFFLE_NONE;
492 if (shufmode == SHUFFLE_AUTO) {
493 if (! makeAutoShuffleList()) {
494 shufmode = SHUFFLE_NONE;
497 mShuffleMode = shufmode;
502 public IBinder onBind(Intent intent) {
503 mDelayedStopHandler.removeCallbacksAndMessages(null);
504 mServiceInUse = true;
509 public void onRebind(Intent intent) {
510 mDelayedStopHandler.removeCallbacksAndMessages(null);
511 mServiceInUse = true;
515 public void onStart(Intent intent, int startId) {
516 mServiceStartId = startId;
517 mDelayedStopHandler.removeCallbacksAndMessages(null);
519 String action = intent.getAction();
520 String cmd = intent.getStringExtra("command");
522 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
524 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
526 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
532 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
534 } else if (CMDSTOP.equals(cmd)) {
539 // make sure the service will shut down on its own if it was
540 // just started but not bound to and nothing is playing
541 mDelayedStopHandler.removeCallbacksAndMessages(null);
542 Message msg = mDelayedStopHandler.obtainMessage();
543 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
547 public boolean onUnbind(Intent intent) {
548 mServiceInUse = false;
550 // Take a snapshot of the current playlist
553 if (isPlaying() || mResumeAfterCall) {
554 // something is currently playing, or will be playing once
555 // an in-progress call ends, so don't stop the service now.
559 // If there is a playlist but playback is paused, then wait a while
560 // before stopping the service, so that pause/resume isn't slow.
561 // Also delay stopping the service if we're transitioning between tracks.
562 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
563 Message msg = mDelayedStopHandler.obtainMessage();
564 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
568 // No active playlist, OK to stop the service right now
569 stopSelf(mServiceStartId);
573 private Handler mDelayedStopHandler = new Handler() {
575 public void handleMessage(Message msg) {
576 // Check again to make sure nothing is playing right now
577 if (isPlaying() || mResumeAfterCall || mServiceInUse
578 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
581 // save the queue again, because it might have changed
582 // since the user exited the music app (because of
583 // party-shuffle or because the play-position changed)
585 stopSelf(mServiceStartId);
590 * Called when we receive a ACTION_MEDIA_EJECT notification.
592 * @param storagePath path to mount point for the removed media
594 public void closeExternalStorageFiles(String storagePath) {
595 // stop playback and clean up if the SD card is going to be unmounted.
597 notifyChange(QUEUE_CHANGED);
598 notifyChange(META_CHANGED);
602 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
603 * The intent will call closeExternalStorageFiles() if the external media
604 * is going to be ejected, so applications can clean up any files they have open.
606 public void registerExternalStorageListener() {
607 if (mUnmountReceiver == null) {
608 mUnmountReceiver = new BroadcastReceiver() {
610 public void onReceive(Context context, Intent intent) {
611 String action = intent.getAction();
612 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
614 mOneShot = true; // This makes us not save the state again later,
615 // which would be wrong because the song ids and
616 // card id might not match.
617 closeExternalStorageFiles(intent.getData().getPath());
618 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
619 mMediaMountedCount++;
620 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
622 notifyChange(QUEUE_CHANGED);
623 notifyChange(META_CHANGED);
627 IntentFilter iFilter = new IntentFilter();
628 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
629 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
630 iFilter.addDataScheme("file");
631 registerReceiver(mUnmountReceiver, iFilter);
636 * Notify the change-receivers that something has changed.
637 * The intent that is sent contains the following data
638 * for the currently playing track:
639 * "id" - Integer: the database row ID
640 * "artist" - String: the name of the artist
641 * "album" - String: the name of the album
642 * "track" - String: the name of the track
643 * The intent has an action that is one of
644 * "com.android.music.metachanged"
645 * "com.android.music.queuechanged",
646 * "com.android.music.playbackcomplete"
647 * "com.android.music.playstatechanged"
648 * respectively indicating that a new track has
649 * started playing, that the playback queue has
650 * changed, that playback has stopped because
651 * the last file in the list has been played,
652 * or that the play-state changed (paused/resumed).
654 private void notifyChange(String what) {
656 Intent i = new Intent(what);
657 i.putExtra("id", Integer.valueOf(getAudioId()));
658 i.putExtra("artist", getArtistName());
659 i.putExtra("album",getAlbumName());
660 i.putExtra("track", getTrackName());
663 if (what.equals(QUEUE_CHANGED)) {
669 // Share this notification directly with our widgets
670 mAppWidgetProvider.notifyChange(this, what);
673 private void ensurePlayListCapacity(int size) {
674 if (mPlayList == null || size > mPlayList.length) {
675 // reallocate at 2x requested size so we don't
676 // need to grow and copy the array for every
678 int [] newlist = new int[size * 2];
679 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
680 for (int i = 0; i < len; i++) {
681 newlist[i] = mPlayList[i];
685 // FIXME: shrink the array when the needed size is much smaller
686 // than the allocated size
689 // insert the list of songs at the specified position in the playlist
690 private void addToPlayList(int [] list, int position) {
691 int addlen = list.length;
692 if (position < 0) { // overwrite
696 ensurePlayListCapacity(mPlayListLen + addlen);
697 if (position > mPlayListLen) {
698 position = mPlayListLen;
701 // move part of list after insertion point
702 int tailsize = mPlayListLen - position;
703 for (int i = tailsize ; i > 0 ; i--) {
704 mPlayList[position + i] = mPlayList[position + i - addlen];
707 // copy list into playlist
708 for (int i = 0; i < addlen; i++) {
709 mPlayList[position + i] = list[i];
711 mPlayListLen += addlen;
715 * Appends a list of tracks to the current playlist.
716 * If nothing is playing currently, playback will be started at
718 * If the action is NOW, playback will switch to the first of
719 * the new tracks immediately.
720 * @param list The list of tracks to append.
721 * @param action NOW, NEXT or LAST
723 public void enqueue(int [] list, int action) {
725 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
726 addToPlayList(list, mPlayPos + 1);
727 notifyChange(QUEUE_CHANGED);
729 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
730 addToPlayList(list, Integer.MAX_VALUE);
731 notifyChange(QUEUE_CHANGED);
733 mPlayPos = mPlayListLen - list.length;
736 notifyChange(META_CHANGED);
744 notifyChange(META_CHANGED);
750 * Replaces the current playlist with a new list,
751 * and prepares for starting playback at the specified
752 * position in the list, or a random position if the
753 * specified position is 0.
754 * @param list The new list of tracks.
756 public void open(int [] list, int position) {
757 synchronized (this) {
758 if (mShuffleMode == SHUFFLE_AUTO) {
759 mShuffleMode = SHUFFLE_NORMAL;
761 int oldId = getAudioId();
762 int listlength = list.length;
763 boolean newlist = true;
764 if (mPlayListLen == listlength) {
765 // possible fast path: list might be the same
767 for (int i = 0; i < listlength; i++) {
768 if (list[i] != mPlayList[i]) {
775 addToPlayList(list, -1);
776 notifyChange(QUEUE_CHANGED);
778 int oldpos = mPlayPos;
782 mPlayPos = mRand.nextInt(mPlayListLen);
786 saveBookmarkIfNeeded();
788 if (oldId != getAudioId()) {
789 notifyChange(META_CHANGED);
795 * Moves the item at index1 to index2.
799 public void moveQueueItem(int index1, int index2) {
800 synchronized (this) {
801 if (index1 >= mPlayListLen) {
802 index1 = mPlayListLen - 1;
804 if (index2 >= mPlayListLen) {
805 index2 = mPlayListLen - 1;
807 if (index1 < index2) {
808 int tmp = mPlayList[index1];
809 for (int i = index1; i < index2; i++) {
810 mPlayList[i] = mPlayList[i+1];
812 mPlayList[index2] = tmp;
813 if (mPlayPos == index1) {
815 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
818 } else if (index2 < index1) {
819 int tmp = mPlayList[index1];
820 for (int i = index1; i > index2; i--) {
821 mPlayList[i] = mPlayList[i-1];
823 mPlayList[index2] = tmp;
824 if (mPlayPos == index1) {
826 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
830 notifyChange(QUEUE_CHANGED);
835 * Returns the current play list
836 * @return An array of integers containing the IDs of the tracks in the play list
838 public int [] getQueue() {
839 synchronized (this) {
840 int len = mPlayListLen;
841 int [] list = new int[len];
842 for (int i = 0; i < len; i++) {
843 list[i] = mPlayList[i];
849 private void openCurrent() {
850 synchronized (this) {
851 if (mCursor != null) {
855 if (mPlayListLen == 0) {
860 String id = String.valueOf(mPlayList[mPlayPos]);
862 mCursor = getContentResolver().query(
863 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
864 mCursorCols, "_id=" + id , null, null);
865 if (mCursor != null) {
866 mCursor.moveToFirst();
867 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
868 // go to bookmark if needed
870 long bookmark = getBookmark();
871 // Start playing a little bit before the bookmark,
872 // so it's easier to get back in to the narrative.
873 seek(bookmark - 5000);
879 public void openAsync(String path) {
880 synchronized (this) {
885 mRepeatMode = REPEAT_NONE;
886 ensurePlayListCapacity(1);
892 mPlayer.setDataSourceAsync(mFileToPlay);
898 * Opens the specified file and readies it for playback.
900 * @param path The full path of the file to be opened.
901 * @param oneshot when set to true, playback will stop after this file completes, instead
902 * of moving on to the next track in the list
904 public void open(String path, boolean oneshot) {
905 synchronized (this) {
911 mRepeatMode = REPEAT_NONE;
912 ensurePlayListCapacity(1);
917 // if mCursor is null, try to associate path with a database cursor
918 if (mCursor == null) {
920 ContentResolver resolver = getContentResolver();
923 String selectionArgs[];
924 if (path.startsWith("content://media/")) {
925 uri = Uri.parse(path);
927 selectionArgs = null;
929 uri = MediaStore.Audio.Media.getContentUriForPath(path);
930 where = MediaStore.Audio.Media.DATA + "=?";
931 selectionArgs = new String[] { path };
935 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
936 if (mCursor != null) {
937 if (mCursor.getCount() == 0) {
941 mCursor.moveToNext();
942 ensurePlayListCapacity(1);
944 mPlayList[0] = mCursor.getInt(IDCOLIDX);
948 } catch (UnsupportedOperationException ex) {
952 mPlayer.setDataSource(mFileToPlay);
954 if (! mPlayer.isInitialized()) {
956 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
957 // beware: this ends up being recursive because next() calls open() again.
960 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
961 // need to make sure we only shows this once
962 mOpenFailedCounter = 0;
964 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
968 mOpenFailedCounter = 0;
974 * Starts playback of a previously opened file.
977 if (mPlayer.isInitialized()) {
978 // if we are at the end of the song, go to the next song first
979 long duration = mPlayer.duration();
980 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
981 mPlayer.position() >= duration - 2000) {
988 NotificationManager nm = (NotificationManager)
989 getSystemService(Context.NOTIFICATION_SERVICE);
991 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
992 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
993 if (getAudioId() < 0) {
995 views.setTextViewText(R.id.trackname, getPath());
996 views.setTextViewText(R.id.artistalbum, null);
998 String artist = getArtistName();
999 views.setTextViewText(R.id.trackname, getTrackName());
1000 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
1001 artist = getString(R.string.unknown_artist_name);
1003 String album = getAlbumName();
1004 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1005 album = getString(R.string.unknown_album_name);
1008 views.setTextViewText(R.id.artistalbum,
1009 getString(R.string.notification_artist_album, artist, album)
1013 Notification status = new Notification();
1014 status.contentView = views;
1015 status.flags |= Notification.FLAG_ONGOING_EVENT;
1016 status.icon = R.drawable.stat_notify_musicplayer;
1017 status.contentIntent = PendingIntent.getActivity(this, 0,
1018 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1019 nm.notify(PLAYBACKSERVICE_STATUS, status);
1020 if (!mIsSupposedToBePlaying) {
1021 notifyChange(PLAYSTATE_CHANGED);
1023 mIsSupposedToBePlaying = true;
1024 } else if (mPlayListLen <= 0) {
1025 // This is mostly so that if you press 'play' on a bluetooth headset
1026 // without every having played anything before, it will still play
1028 setShuffleMode(SHUFFLE_AUTO);
1032 private void stop(boolean remove_status_icon) {
1033 if (mPlayer.isInitialized()) {
1037 if (mCursor != null) {
1041 if (remove_status_icon) {
1044 setForeground(false);
1045 if (remove_status_icon) {
1046 mIsSupposedToBePlaying = false;
1053 public void stop() {
1058 * Pauses playback (call play() to resume)
1060 public void pause() {
1061 synchronized(this) {
1065 setForeground(false);
1066 mIsSupposedToBePlaying = false;
1067 notifyChange(PLAYSTATE_CHANGED);
1068 saveBookmarkIfNeeded();
1073 /** Returns whether something is currently playing
1075 * @return true if something is playing (or will be playing shortly, in case
1076 * we're currently transitioning between tracks), false if not.
1078 public boolean isPlaying() {
1079 return mIsSupposedToBePlaying;
1083 Desired behavior for prev/next/shuffle:
1085 - NEXT will move to the next track in the list when not shuffling, and to
1086 a track randomly picked from the not-yet-played tracks when shuffling.
1087 If all tracks have already been played, pick from the full set, but
1088 avoid picking the previously played track if possible.
1089 - when shuffling, PREV will go to the previously played track. Hitting PREV
1090 again will go to the track played before that, etc. When the start of the
1091 history has been reached, PREV is a no-op.
1092 When not shuffling, PREV will go to the sequentially previous track (the
1093 difference with the shuffle-case is mainly that when not shuffling, the
1094 user can back up to tracks that are not in the history).
1097 When playing an album with 10 tracks from the start, and enabling shuffle
1098 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1099 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1100 When hitting 'prev' 8 times while playing track 7 in this example, the
1101 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1102 a random track will be picked again. If at any time user disables shuffling
1103 the next/previous track will be picked in sequential order again.
1106 public void prev() {
1107 synchronized (this) {
1109 // we were playing a specific file not part of a playlist, so there is no 'previous'
1114 if (mShuffleMode == SHUFFLE_NORMAL) {
1115 // go to previously-played track and remove it from the history
1116 int histsize = mHistory.size();
1117 if (histsize == 0) {
1121 Integer pos = mHistory.remove(histsize - 1);
1122 mPlayPos = pos.intValue();
1127 mPlayPos = mPlayListLen - 1;
1130 saveBookmarkIfNeeded();
1134 notifyChange(META_CHANGED);
1138 public void next(boolean force) {
1139 synchronized (this) {
1141 // we were playing a specific file not part of a playlist, so there is no 'next'
1147 if (mPlayListLen <= 0) {
1151 // Store the current file in the history, but keep the history at a
1153 if (mPlayPos >= 0) {
1154 mHistory.add(Integer.valueOf(mPlayPos));
1156 if (mHistory.size() > MAX_HISTORY_SIZE) {
1157 mHistory.removeElementAt(0);
1160 if (mShuffleMode == SHUFFLE_NORMAL) {
1161 // Pick random next track from the not-yet-played ones
1162 // TODO: make it work right after adding/removing items in the queue.
1164 int numTracks = mPlayListLen;
1165 int[] tracks = new int[numTracks];
1166 for (int i=0;i < numTracks; i++) {
1170 int numHistory = mHistory.size();
1171 int numUnplayed = numTracks;
1172 for (int i=0;i < numHistory; i++) {
1173 int idx = mHistory.get(i).intValue();
1174 if (idx < numTracks && tracks[idx] >= 0) {
1180 // 'numUnplayed' now indicates how many tracks have not yet
1181 // been played, and 'tracks' contains the indices of those
1183 if (numUnplayed <=0) {
1184 // everything's already been played
1185 if (mRepeatMode == REPEAT_ALL || force) {
1186 //pick from full set
1187 numUnplayed = numTracks;
1188 for (int i=0;i < numTracks; i++) {
1197 int skip = mRand.nextInt(numUnplayed);
1200 while (tracks[++cnt] < 0)
1208 } else if (mShuffleMode == SHUFFLE_AUTO) {
1209 doAutoShuffleUpdate();
1212 if (mPlayPos >= mPlayListLen - 1) {
1213 // we're at the end of the list
1214 if (mRepeatMode == REPEAT_NONE && !force) {
1217 notifyChange(PLAYBACK_COMPLETE);
1218 mIsSupposedToBePlaying = false;
1220 } else if (mRepeatMode == REPEAT_ALL || force) {
1227 saveBookmarkIfNeeded();
1231 notifyChange(META_CHANGED);
1235 private void gotoIdleState() {
1236 NotificationManager nm =
1237 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1238 nm.cancel(PLAYBACKSERVICE_STATUS);
1239 mDelayedStopHandler.removeCallbacksAndMessages(null);
1240 Message msg = mDelayedStopHandler.obtainMessage();
1241 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1244 private void saveBookmarkIfNeeded() {
1247 long pos = position();
1248 long bookmark = getBookmark();
1249 long duration = duration();
1250 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1251 (pos > bookmark && (pos - 10000) < bookmark)) {
1252 // The existing bookmark is close to the current
1253 // position, so don't update it.
1256 if (pos < 15000 || (pos + 10000) > duration) {
1257 // if we're near the start or end, clear the bookmark
1261 // write 'pos' to the bookmark field
1262 ContentValues values = new ContentValues();
1263 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1264 Uri uri = ContentUris.withAppendedId(
1265 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1266 getContentResolver().update(uri, values, null, null);
1268 } catch (SQLiteException ex) {
1272 // Make sure there are at least 5 items after the currently playing item
1273 // and no more than 10 items before.
1274 private void doAutoShuffleUpdate() {
1275 boolean notify = false;
1276 // remove old entries
1277 if (mPlayPos > 10) {
1278 removeTracks(0, mPlayPos - 9);
1281 // add new entries if needed
1282 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1283 for (int i = 0; i < to_add; i++) {
1284 // pick something at random from the list
1285 int idx = mRand.nextInt(mAutoShuffleList.length);
1286 Integer which = mAutoShuffleList[idx];
1287 ensurePlayListCapacity(mPlayListLen + 1);
1288 mPlayList[mPlayListLen++] = which;
1292 notifyChange(QUEUE_CHANGED);
1296 // A simple variation of Random that makes sure that the
1297 // value it returns is not equal to the value it returned
1298 // previously, unless the interval is 1.
1299 private static class Shuffler {
1300 private int mPrevious;
1301 private Random mRandom = new Random();
1302 public int nextInt(int interval) {
1305 ret = mRandom.nextInt(interval);
1306 } while (ret == mPrevious && interval > 1);
1312 private boolean makeAutoShuffleList() {
1313 ContentResolver res = getContentResolver();
1316 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1317 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1319 if (c == null || c.getCount() == 0) {
1322 int len = c.getCount();
1323 int[] list = new int[len];
1324 for (int i = 0; i < len; i++) {
1326 list[i] = c.getInt(0);
1328 mAutoShuffleList = list;
1330 } catch (RuntimeException ex) {
1340 * Removes the range of tracks specified from the play list. If a file within the range is
1341 * the file currently being played, playback will move to the next file after the
1343 * @param first The first file to be removed
1344 * @param last The last file to be removed
1345 * @return the number of tracks deleted
1347 public int removeTracks(int first, int last) {
1348 int numremoved = removeTracksInternal(first, last);
1349 if (numremoved > 0) {
1350 notifyChange(QUEUE_CHANGED);
1355 private int removeTracksInternal(int first, int last) {
1356 synchronized (this) {
1357 if (last < first) return 0;
1358 if (first < 0) first = 0;
1359 if (last >= mPlayListLen) last = mPlayListLen - 1;
1361 boolean gotonext = false;
1362 if (first <= mPlayPos && mPlayPos <= last) {
1365 } else if (mPlayPos > last) {
1366 mPlayPos -= (last - first + 1);
1368 int num = mPlayListLen - last - 1;
1369 for (int i = 0; i < num; i++) {
1370 mPlayList[first + i] = mPlayList[last + 1 + i];
1372 mPlayListLen -= last - first + 1;
1375 if (mPlayListLen == 0) {
1379 if (mPlayPos >= mPlayListLen) {
1382 boolean wasPlaying = isPlaying();
1390 return last - first + 1;
1395 * Removes all instances of the track with the given id
1396 * from the playlist.
1397 * @param id The id to be removed
1398 * @return how many instances of the track were removed
1400 public int removeTrack(int id) {
1402 synchronized (this) {
1403 for (int i = 0; i < mPlayListLen; i++) {
1404 if (mPlayList[i] == id) {
1405 numremoved += removeTracksInternal(i, i);
1410 if (numremoved > 0) {
1411 notifyChange(QUEUE_CHANGED);
1416 public void setShuffleMode(int shufflemode) {
1417 synchronized(this) {
1418 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1421 mShuffleMode = shufflemode;
1422 if (mShuffleMode == SHUFFLE_AUTO) {
1423 if (makeAutoShuffleList()) {
1425 doAutoShuffleUpdate();
1429 notifyChange(META_CHANGED);
1432 // failed to build a list of files to shuffle
1433 mShuffleMode = SHUFFLE_NONE;
1439 public int getShuffleMode() {
1440 return mShuffleMode;
1443 public void setRepeatMode(int repeatmode) {
1444 synchronized(this) {
1445 mRepeatMode = repeatmode;
1449 public int getRepeatMode() {
1453 public int getMediaMountedCount() {
1454 return mMediaMountedCount;
1458 * Returns the path of the currently playing file, or null if
1459 * no file is currently playing.
1461 public String getPath() {
1466 * Returns the rowid of the currently playing file, or -1 if
1467 * no file is currently playing.
1469 public int getAudioId() {
1470 synchronized (this) {
1471 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1472 return mPlayList[mPlayPos];
1479 * Returns the position in the queue
1480 * @return the position in the queue
1482 public int getQueuePosition() {
1483 synchronized(this) {
1489 * Starts playing the track at the given position in the queue.
1490 * @param pos The position in the queue of the track that will be played.
1492 public void setQueuePosition(int pos) {
1493 synchronized(this) {
1498 notifyChange(META_CHANGED);
1502 public String getArtistName() {
1503 synchronized(this) {
1504 if (mCursor == null) {
1507 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1511 public int getArtistId() {
1512 synchronized (this) {
1513 if (mCursor == null) {
1516 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1520 public String getAlbumName() {
1521 synchronized (this) {
1522 if (mCursor == null) {
1525 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1529 public int getAlbumId() {
1530 synchronized (this) {
1531 if (mCursor == null) {
1534 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1538 public String getTrackName() {
1539 synchronized (this) {
1540 if (mCursor == null) {
1543 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1547 private boolean isPodcast() {
1548 synchronized (this) {
1549 if (mCursor == null) {
1552 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1556 private long getBookmark() {
1557 synchronized (this) {
1558 if (mCursor == null) {
1561 return mCursor.getLong(BOOKMARKCOLIDX);
1566 * Returns the duration of the file in milliseconds.
1567 * Currently this method returns -1 for the duration of MIDI files.
1569 public long duration() {
1570 if (mPlayer.isInitialized()) {
1571 return mPlayer.duration();
1577 * Returns the current playback position in milliseconds
1579 public long position() {
1580 if (mPlayer.isInitialized()) {
1581 return mPlayer.position();
1587 * Seeks to the position specified.
1589 * @param pos The position to seek to, in milliseconds
1591 public long seek(long pos) {
1592 if (mPlayer.isInitialized()) {
1593 if (pos < 0) pos = 0;
1594 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1595 return mPlayer.seek(pos);
1601 * Provides a unified interface for dealing with midi files and
1602 * other media files.
1604 private class MultiPlayer {
1605 private MediaPlayer mMediaPlayer = new MediaPlayer();
1606 private Handler mHandler;
1607 private boolean mIsInitialized = false;
1609 public MultiPlayer() {
1610 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1613 public void setDataSourceAsync(String path) {
1615 mMediaPlayer.reset();
1616 mMediaPlayer.setDataSource(path);
1617 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1618 mMediaPlayer.setOnPreparedListener(preparedlistener);
1619 mMediaPlayer.prepareAsync();
1620 } catch (IOException ex) {
1621 // TODO: notify the user why the file couldn't be opened
1622 mIsInitialized = false;
1624 } catch (IllegalArgumentException ex) {
1625 // TODO: notify the user why the file couldn't be opened
1626 mIsInitialized = false;
1629 mMediaPlayer.setOnCompletionListener(listener);
1630 mMediaPlayer.setOnErrorListener(errorListener);
1632 mIsInitialized = true;
1635 public void setDataSource(String path) {
1637 mMediaPlayer.reset();
1638 mMediaPlayer.setOnPreparedListener(null);
1639 if (path.startsWith("content://")) {
1640 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1642 mMediaPlayer.setDataSource(path);
1644 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1645 mMediaPlayer.prepare();
1646 } catch (IOException ex) {
1647 // TODO: notify the user why the file couldn't be opened
1648 mIsInitialized = false;
1650 } catch (IllegalArgumentException ex) {
1651 // TODO: notify the user why the file couldn't be opened
1652 mIsInitialized = false;
1655 mMediaPlayer.setOnCompletionListener(listener);
1656 mMediaPlayer.setOnErrorListener(errorListener);
1658 mIsInitialized = true;
1661 public boolean isInitialized() {
1662 return mIsInitialized;
1665 public void start() {
1666 mMediaPlayer.start();
1669 public void stop() {
1670 mMediaPlayer.reset();
1671 mIsInitialized = false;
1675 * You CANNOT use this player anymore after calling release()
1677 public void release() {
1679 mMediaPlayer.release();
1682 public void pause() {
1683 mMediaPlayer.pause();
1686 public void setHandler(Handler handler) {
1690 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1691 public void onCompletion(MediaPlayer mp) {
1692 // Acquire a temporary wakelock, since when we return from
1693 // this callback the MediaPlayer will release its wakelock
1694 // and allow the device to go to sleep.
1695 // This temporary wakelock is released when the RELEASE_WAKELOCK
1696 // message is processed, but just in case, put a timeout on it.
1697 mWakeLock.acquire(30000);
1698 mHandler.sendEmptyMessage(TRACK_ENDED);
1699 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1703 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1704 public void onPrepared(MediaPlayer mp) {
1705 notifyChange(ASYNC_OPEN_COMPLETE);
1709 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1710 public boolean onError(MediaPlayer mp, int what, int extra) {
1712 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1713 mIsInitialized = false;
1714 mMediaPlayer.release();
1715 // Creating a new MediaPlayer and settings its wakemode does not
1716 // require the media service, so it's OK to do this now, while the
1717 // service is still being restarted
1718 mMediaPlayer = new MediaPlayer();
1719 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1720 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1729 public long duration() {
1730 return mMediaPlayer.getDuration();
1733 public long position() {
1734 return mMediaPlayer.getCurrentPosition();
1737 public long seek(long whereto) {
1738 mMediaPlayer.seekTo((int) whereto);
1742 public void setVolume(float vol) {
1743 mMediaPlayer.setVolume(vol, vol);
1748 * By making this a static class with a WeakReference to the Service, we
1749 * ensure that the Service can be GCd even when the system process still
1750 * has a remote reference to the stub.
1752 static class ServiceStub extends IMediaPlaybackService.Stub {
1753 WeakReference<MediaPlaybackService> mService;
1755 ServiceStub(MediaPlaybackService service) {
1756 mService = new WeakReference<MediaPlaybackService>(service);
1759 public void openFileAsync(String path)
1761 mService.get().openAsync(path);
1763 public void openFile(String path, boolean oneShot)
1765 mService.get().open(path, oneShot);
1767 public void open(int [] list, int position) {
1768 mService.get().open(list, position);
1770 public int getQueuePosition() {
1771 return mService.get().getQueuePosition();
1773 public void setQueuePosition(int index) {
1774 mService.get().setQueuePosition(index);
1776 public boolean isPlaying() {
1777 return mService.get().isPlaying();
1779 public void stop() {
1780 mService.get().stop();
1782 public void pause() {
1783 mService.get().pause();
1785 public void play() {
1786 mService.get().play();
1788 public void prev() {
1789 mService.get().prev();
1791 public void next() {
1792 mService.get().next(true);
1794 public String getTrackName() {
1795 return mService.get().getTrackName();
1797 public String getAlbumName() {
1798 return mService.get().getAlbumName();
1800 public int getAlbumId() {
1801 return mService.get().getAlbumId();
1803 public String getArtistName() {
1804 return mService.get().getArtistName();
1806 public int getArtistId() {
1807 return mService.get().getArtistId();
1809 public void enqueue(int [] list , int action) {
1810 mService.get().enqueue(list, action);
1812 public int [] getQueue() {
1813 return mService.get().getQueue();
1815 public void moveQueueItem(int from, int to) {
1816 mService.get().moveQueueItem(from, to);
1818 public String getPath() {
1819 return mService.get().getPath();
1821 public int getAudioId() {
1822 return mService.get().getAudioId();
1824 public long position() {
1825 return mService.get().position();
1827 public long duration() {
1828 return mService.get().duration();
1830 public long seek(long pos) {
1831 return mService.get().seek(pos);
1833 public void setShuffleMode(int shufflemode) {
1834 mService.get().setShuffleMode(shufflemode);
1836 public int getShuffleMode() {
1837 return mService.get().getShuffleMode();
1839 public int removeTracks(int first, int last) {
1840 return mService.get().removeTracks(first, last);
1842 public int removeTrack(int id) {
1843 return mService.get().removeTrack(id);
1845 public void setRepeatMode(int repeatmode) {
1846 mService.get().setRepeatMode(repeatmode);
1848 public int getRepeatMode() {
1849 return mService.get().getRepeatMode();
1851 public int getMediaMountedCount() {
1852 return mService.get().getMediaMountedCount();
1857 private final IBinder mBinder = new ServiceStub(this);