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 if (mRepeatMode != REPEAT_CURRENT &&
980 mPlayer.position() >= mPlayer.duration() - 2000) {
987 NotificationManager nm = (NotificationManager)
988 getSystemService(Context.NOTIFICATION_SERVICE);
990 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
991 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
992 if (getAudioId() < 0) {
994 views.setTextViewText(R.id.trackname, getPath());
995 views.setTextViewText(R.id.artistalbum, null);
997 String artist = getArtistName();
998 views.setTextViewText(R.id.trackname, getTrackName());
999 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
1000 artist = getString(R.string.unknown_artist_name);
1002 String album = getAlbumName();
1003 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1004 album = getString(R.string.unknown_album_name);
1007 views.setTextViewText(R.id.artistalbum,
1008 getString(R.string.notification_artist_album, artist, album)
1012 Notification status = new Notification();
1013 status.contentView = views;
1014 status.flags |= Notification.FLAG_ONGOING_EVENT;
1015 status.icon = R.drawable.stat_notify_musicplayer;
1016 status.contentIntent = PendingIntent.getActivity(this, 0,
1017 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1018 nm.notify(PLAYBACKSERVICE_STATUS, status);
1019 if (!mIsSupposedToBePlaying) {
1020 notifyChange(PLAYSTATE_CHANGED);
1022 mIsSupposedToBePlaying = true;
1023 } else if (mPlayListLen <= 0) {
1024 // This is mostly so that if you press 'play' on a bluetooth headset
1025 // without every having played anything before, it will still play
1027 setShuffleMode(SHUFFLE_AUTO);
1031 private void stop(boolean remove_status_icon) {
1032 if (mPlayer.isInitialized()) {
1036 if (mCursor != null) {
1040 if (remove_status_icon) {
1043 setForeground(false);
1044 if (remove_status_icon) {
1045 mIsSupposedToBePlaying = false;
1052 public void stop() {
1057 * Pauses playback (call play() to resume)
1059 public void pause() {
1060 synchronized(this) {
1064 setForeground(false);
1065 mIsSupposedToBePlaying = false;
1066 notifyChange(PLAYSTATE_CHANGED);
1067 saveBookmarkIfNeeded();
1072 /** Returns whether something is currently playing
1074 * @return true if something is playing (or will be playing shortly, in case
1075 * we're currently transitioning between tracks), false if not.
1077 public boolean isPlaying() {
1078 return mIsSupposedToBePlaying;
1082 Desired behavior for prev/next/shuffle:
1084 - NEXT will move to the next track in the list when not shuffling, and to
1085 a track randomly picked from the not-yet-played tracks when shuffling.
1086 If all tracks have already been played, pick from the full set, but
1087 avoid picking the previously played track if possible.
1088 - when shuffling, PREV will go to the previously played track. Hitting PREV
1089 again will go to the track played before that, etc. When the start of the
1090 history has been reached, PREV is a no-op.
1091 When not shuffling, PREV will go to the sequentially previous track (the
1092 difference with the shuffle-case is mainly that when not shuffling, the
1093 user can back up to tracks that are not in the history).
1096 When playing an album with 10 tracks from the start, and enabling shuffle
1097 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1098 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1099 When hitting 'prev' 8 times while playing track 7 in this example, the
1100 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1101 a random track will be picked again. If at any time user disables shuffling
1102 the next/previous track will be picked in sequential order again.
1105 public void prev() {
1106 synchronized (this) {
1108 // we were playing a specific file not part of a playlist, so there is no 'previous'
1113 if (mShuffleMode == SHUFFLE_NORMAL) {
1114 // go to previously-played track and remove it from the history
1115 int histsize = mHistory.size();
1116 if (histsize == 0) {
1120 Integer pos = mHistory.remove(histsize - 1);
1121 mPlayPos = pos.intValue();
1126 mPlayPos = mPlayListLen - 1;
1129 saveBookmarkIfNeeded();
1133 notifyChange(META_CHANGED);
1137 public void next(boolean force) {
1138 synchronized (this) {
1140 // we were playing a specific file not part of a playlist, so there is no 'next'
1146 if (mPlayListLen <= 0) {
1150 // Store the current file in the history, but keep the history at a
1152 if (mPlayPos >= 0) {
1153 mHistory.add(Integer.valueOf(mPlayPos));
1155 if (mHistory.size() > MAX_HISTORY_SIZE) {
1156 mHistory.removeElementAt(0);
1159 if (mShuffleMode == SHUFFLE_NORMAL) {
1160 // Pick random next track from the not-yet-played ones
1161 // TODO: make it work right after adding/removing items in the queue.
1163 int numTracks = mPlayListLen;
1164 int[] tracks = new int[numTracks];
1165 for (int i=0;i < numTracks; i++) {
1169 int numHistory = mHistory.size();
1170 int numUnplayed = numTracks;
1171 for (int i=0;i < numHistory; i++) {
1172 int idx = mHistory.get(i).intValue();
1173 if (idx < numTracks && tracks[idx] >= 0) {
1179 // 'numUnplayed' now indicates how many tracks have not yet
1180 // been played, and 'tracks' contains the indices of those
1182 if (numUnplayed <=0) {
1183 // everything's already been played
1184 if (mRepeatMode == REPEAT_ALL || force) {
1185 //pick from full set
1186 numUnplayed = numTracks;
1187 for (int i=0;i < numTracks; i++) {
1196 int skip = mRand.nextInt(numUnplayed);
1199 while (tracks[++cnt] < 0)
1207 } else if (mShuffleMode == SHUFFLE_AUTO) {
1208 doAutoShuffleUpdate();
1211 if (mPlayPos >= mPlayListLen - 1) {
1212 // we're at the end of the list
1213 if (mRepeatMode == REPEAT_NONE && !force) {
1216 notifyChange(PLAYBACK_COMPLETE);
1217 mIsSupposedToBePlaying = false;
1219 } else if (mRepeatMode == REPEAT_ALL || force) {
1226 saveBookmarkIfNeeded();
1230 notifyChange(META_CHANGED);
1234 private void gotoIdleState() {
1235 NotificationManager nm =
1236 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1237 nm.cancel(PLAYBACKSERVICE_STATUS);
1238 mDelayedStopHandler.removeCallbacksAndMessages(null);
1239 Message msg = mDelayedStopHandler.obtainMessage();
1240 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1243 private void saveBookmarkIfNeeded() {
1246 long pos = position();
1247 long bookmark = getBookmark();
1248 long duration = duration();
1249 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1250 (pos > bookmark && (pos - 10000) < bookmark)) {
1251 // The existing bookmark is close to the current
1252 // position, so don't update it.
1255 if (pos < 15000 || (pos + 10000) > duration) {
1256 // if we're near the start or end, clear the bookmark
1260 // write 'pos' to the bookmark field
1261 ContentValues values = new ContentValues();
1262 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1263 Uri uri = ContentUris.withAppendedId(
1264 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1265 getContentResolver().update(uri, values, null, null);
1267 } catch (SQLiteException ex) {
1271 // Make sure there are at least 5 items after the currently playing item
1272 // and no more than 10 items before.
1273 private void doAutoShuffleUpdate() {
1274 boolean notify = false;
1275 // remove old entries
1276 if (mPlayPos > 10) {
1277 removeTracks(0, mPlayPos - 9);
1280 // add new entries if needed
1281 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1282 for (int i = 0; i < to_add; i++) {
1283 // pick something at random from the list
1284 int idx = mRand.nextInt(mAutoShuffleList.length);
1285 Integer which = mAutoShuffleList[idx];
1286 ensurePlayListCapacity(mPlayListLen + 1);
1287 mPlayList[mPlayListLen++] = which;
1291 notifyChange(QUEUE_CHANGED);
1295 // A simple variation of Random that makes sure that the
1296 // value it returns is not equal to the value it returned
1297 // previously, unless the interval is 1.
1298 private static class Shuffler {
1299 private int mPrevious;
1300 private Random mRandom = new Random();
1301 public int nextInt(int interval) {
1304 ret = mRandom.nextInt(interval);
1305 } while (ret == mPrevious && interval > 1);
1311 private boolean makeAutoShuffleList() {
1312 ContentResolver res = getContentResolver();
1315 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1316 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1318 if (c == null || c.getCount() == 0) {
1321 int len = c.getCount();
1322 int[] list = new int[len];
1323 for (int i = 0; i < len; i++) {
1325 list[i] = c.getInt(0);
1327 mAutoShuffleList = list;
1329 } catch (RuntimeException ex) {
1339 * Removes the range of tracks specified from the play list. If a file within the range is
1340 * the file currently being played, playback will move to the next file after the
1342 * @param first The first file to be removed
1343 * @param last The last file to be removed
1344 * @return the number of tracks deleted
1346 public int removeTracks(int first, int last) {
1347 int numremoved = removeTracksInternal(first, last);
1348 if (numremoved > 0) {
1349 notifyChange(QUEUE_CHANGED);
1354 private int removeTracksInternal(int first, int last) {
1355 synchronized (this) {
1356 if (last < first) return 0;
1357 if (first < 0) first = 0;
1358 if (last >= mPlayListLen) last = mPlayListLen - 1;
1360 boolean gotonext = false;
1361 if (first <= mPlayPos && mPlayPos <= last) {
1364 } else if (mPlayPos > last) {
1365 mPlayPos -= (last - first + 1);
1367 int num = mPlayListLen - last - 1;
1368 for (int i = 0; i < num; i++) {
1369 mPlayList[first + i] = mPlayList[last + 1 + i];
1371 mPlayListLen -= last - first + 1;
1374 if (mPlayListLen == 0) {
1378 if (mPlayPos >= mPlayListLen) {
1381 boolean wasPlaying = isPlaying();
1389 return last - first + 1;
1394 * Removes all instances of the track with the given id
1395 * from the playlist.
1396 * @param id The id to be removed
1397 * @return how many instances of the track were removed
1399 public int removeTrack(int id) {
1401 synchronized (this) {
1402 for (int i = 0; i < mPlayListLen; i++) {
1403 if (mPlayList[i] == id) {
1404 numremoved += removeTracksInternal(i, i);
1409 if (numremoved > 0) {
1410 notifyChange(QUEUE_CHANGED);
1415 public void setShuffleMode(int shufflemode) {
1416 synchronized(this) {
1417 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1420 mShuffleMode = shufflemode;
1421 if (mShuffleMode == SHUFFLE_AUTO) {
1422 if (makeAutoShuffleList()) {
1424 doAutoShuffleUpdate();
1428 notifyChange(META_CHANGED);
1431 // failed to build a list of files to shuffle
1432 mShuffleMode = SHUFFLE_NONE;
1438 public int getShuffleMode() {
1439 return mShuffleMode;
1442 public void setRepeatMode(int repeatmode) {
1443 synchronized(this) {
1444 mRepeatMode = repeatmode;
1448 public int getRepeatMode() {
1452 public int getMediaMountedCount() {
1453 return mMediaMountedCount;
1457 * Returns the path of the currently playing file, or null if
1458 * no file is currently playing.
1460 public String getPath() {
1465 * Returns the rowid of the currently playing file, or -1 if
1466 * no file is currently playing.
1468 public int getAudioId() {
1469 synchronized (this) {
1470 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1471 return mPlayList[mPlayPos];
1478 * Returns the position in the queue
1479 * @return the position in the queue
1481 public int getQueuePosition() {
1482 synchronized(this) {
1488 * Starts playing the track at the given position in the queue.
1489 * @param pos The position in the queue of the track that will be played.
1491 public void setQueuePosition(int pos) {
1492 synchronized(this) {
1497 notifyChange(META_CHANGED);
1501 public String getArtistName() {
1502 synchronized(this) {
1503 if (mCursor == null) {
1506 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1510 public int getArtistId() {
1511 synchronized (this) {
1512 if (mCursor == null) {
1515 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1519 public String getAlbumName() {
1520 synchronized (this) {
1521 if (mCursor == null) {
1524 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1528 public int getAlbumId() {
1529 synchronized (this) {
1530 if (mCursor == null) {
1533 return mCursor.getInt(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1537 public String getTrackName() {
1538 synchronized (this) {
1539 if (mCursor == null) {
1542 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1546 private boolean isPodcast() {
1547 synchronized (this) {
1548 if (mCursor == null) {
1551 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1555 private long getBookmark() {
1556 synchronized (this) {
1557 if (mCursor == null) {
1560 return mCursor.getLong(BOOKMARKCOLIDX);
1565 * Returns the duration of the file in milliseconds.
1566 * Currently this method returns -1 for the duration of MIDI files.
1568 public long duration() {
1569 if (mPlayer.isInitialized()) {
1570 return mPlayer.duration();
1576 * Returns the current playback position in milliseconds
1578 public long position() {
1579 if (mPlayer.isInitialized()) {
1580 return mPlayer.position();
1586 * Seeks to the position specified.
1588 * @param pos The position to seek to, in milliseconds
1590 public long seek(long pos) {
1591 if (mPlayer.isInitialized()) {
1592 if (pos < 0) pos = 0;
1593 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1594 return mPlayer.seek(pos);
1600 * Provides a unified interface for dealing with midi files and
1601 * other media files.
1603 private class MultiPlayer {
1604 private MediaPlayer mMediaPlayer = new MediaPlayer();
1605 private Handler mHandler;
1606 private boolean mIsInitialized = false;
1608 public MultiPlayer() {
1609 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1612 public void setDataSourceAsync(String path) {
1614 mMediaPlayer.reset();
1615 mMediaPlayer.setDataSource(path);
1616 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1617 mMediaPlayer.setOnPreparedListener(preparedlistener);
1618 mMediaPlayer.prepareAsync();
1619 } catch (IOException ex) {
1620 // TODO: notify the user why the file couldn't be opened
1621 mIsInitialized = false;
1623 } catch (IllegalArgumentException ex) {
1624 // TODO: notify the user why the file couldn't be opened
1625 mIsInitialized = false;
1628 mMediaPlayer.setOnCompletionListener(listener);
1629 mMediaPlayer.setOnErrorListener(errorListener);
1631 mIsInitialized = true;
1634 public void setDataSource(String path) {
1636 mMediaPlayer.reset();
1637 mMediaPlayer.setOnPreparedListener(null);
1638 if (path.startsWith("content://")) {
1639 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1641 mMediaPlayer.setDataSource(path);
1643 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1644 mMediaPlayer.prepare();
1645 } catch (IOException ex) {
1646 // TODO: notify the user why the file couldn't be opened
1647 mIsInitialized = false;
1649 } catch (IllegalArgumentException ex) {
1650 // TODO: notify the user why the file couldn't be opened
1651 mIsInitialized = false;
1654 mMediaPlayer.setOnCompletionListener(listener);
1655 mMediaPlayer.setOnErrorListener(errorListener);
1657 mIsInitialized = true;
1660 public boolean isInitialized() {
1661 return mIsInitialized;
1664 public void start() {
1665 mMediaPlayer.start();
1668 public void stop() {
1669 mMediaPlayer.reset();
1670 mIsInitialized = false;
1674 * You CANNOT use this player anymore after calling release()
1676 public void release() {
1678 mMediaPlayer.release();
1681 public void pause() {
1682 mMediaPlayer.pause();
1685 public void setHandler(Handler handler) {
1689 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1690 public void onCompletion(MediaPlayer mp) {
1691 // Acquire a temporary wakelock, since when we return from
1692 // this callback the MediaPlayer will release its wakelock
1693 // and allow the device to go to sleep.
1694 // This temporary wakelock is released when the RELEASE_WAKELOCK
1695 // message is processed, but just in case, put a timeout on it.
1696 mWakeLock.acquire(30000);
1697 mHandler.sendEmptyMessage(TRACK_ENDED);
1698 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1702 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1703 public void onPrepared(MediaPlayer mp) {
1704 notifyChange(ASYNC_OPEN_COMPLETE);
1708 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1709 public boolean onError(MediaPlayer mp, int what, int extra) {
1711 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1712 mIsInitialized = false;
1713 mMediaPlayer.release();
1714 // Creating a new MediaPlayer and settings its wakemode does not
1715 // require the media service, so it's OK to do this now, while the
1716 // service is still being restarted
1717 mMediaPlayer = new MediaPlayer();
1718 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1719 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1728 public long duration() {
1729 return mMediaPlayer.getDuration();
1732 public long position() {
1733 return mMediaPlayer.getCurrentPosition();
1736 public long seek(long whereto) {
1737 mMediaPlayer.seekTo((int) whereto);
1741 public void setVolume(float vol) {
1742 mMediaPlayer.setVolume(vol, vol);
1747 * By making this a static class with a WeakReference to the Service, we
1748 * ensure that the Service can be GCd even when the system process still
1749 * has a remote reference to the stub.
1751 static class ServiceStub extends IMediaPlaybackService.Stub {
1752 WeakReference<MediaPlaybackService> mService;
1754 ServiceStub(MediaPlaybackService service) {
1755 mService = new WeakReference<MediaPlaybackService>(service);
1758 public void openFileAsync(String path)
1760 mService.get().openAsync(path);
1762 public void openFile(String path, boolean oneShot)
1764 mService.get().open(path, oneShot);
1766 public void open(int [] list, int position) {
1767 mService.get().open(list, position);
1769 public int getQueuePosition() {
1770 return mService.get().getQueuePosition();
1772 public void setQueuePosition(int index) {
1773 mService.get().setQueuePosition(index);
1775 public boolean isPlaying() {
1776 return mService.get().isPlaying();
1778 public void stop() {
1779 mService.get().stop();
1781 public void pause() {
1782 mService.get().pause();
1784 public void play() {
1785 mService.get().play();
1787 public void prev() {
1788 mService.get().prev();
1790 public void next() {
1791 mService.get().next(true);
1793 public String getTrackName() {
1794 return mService.get().getTrackName();
1796 public String getAlbumName() {
1797 return mService.get().getAlbumName();
1799 public int getAlbumId() {
1800 return mService.get().getAlbumId();
1802 public String getArtistName() {
1803 return mService.get().getArtistName();
1805 public int getArtistId() {
1806 return mService.get().getArtistId();
1808 public void enqueue(int [] list , int action) {
1809 mService.get().enqueue(list, action);
1811 public int [] getQueue() {
1812 return mService.get().getQueue();
1814 public void moveQueueItem(int from, int to) {
1815 mService.get().moveQueueItem(from, to);
1817 public String getPath() {
1818 return mService.get().getPath();
1820 public int getAudioId() {
1821 return mService.get().getAudioId();
1823 public long position() {
1824 return mService.get().position();
1826 public long duration() {
1827 return mService.get().duration();
1829 public long seek(long pos) {
1830 return mService.get().seek(pos);
1832 public void setShuffleMode(int shufflemode) {
1833 mService.get().setShuffleMode(shufflemode);
1835 public int getShuffleMode() {
1836 return mService.get().getShuffleMode();
1838 public int removeTracks(int first, int last) {
1839 return mService.get().removeTracks(first, last);
1841 public int removeTrack(int id) {
1842 return mService.get().removeTrack(id);
1844 public void setRepeatMode(int repeatmode) {
1845 mService.get().setRepeatMode(repeatmode);
1847 public int getRepeatMode() {
1848 return mService.get().getRepeatMode();
1850 public int getMediaMountedCount() {
1851 return mService.get().getMediaMountedCount();
1856 private final IBinder mBinder = new ServiceStub(this);