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.PendingIntent;
21 import android.app.Service;
22 import android.appwidget.AppWidgetManager;
23 import android.content.ContentResolver;
24 import android.content.ContentUris;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.BroadcastReceiver;
30 import android.content.SharedPreferences;
31 import android.content.SharedPreferences.Editor;
32 import android.database.Cursor;
33 import android.database.sqlite.SQLiteException;
34 import android.media.AudioManager;
35 import android.media.MediaPlayer;
36 import android.net.Uri;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.Message;
40 import android.os.PowerManager;
41 import android.os.SystemClock;
42 import android.os.PowerManager.WakeLock;
43 import android.provider.MediaStore;
44 import android.telephony.PhoneStateListener;
45 import android.telephony.TelephonyManager;
46 import android.util.Log;
47 import android.widget.RemoteViews;
48 import android.widget.Toast;
50 import java.io.IOException;
51 import java.lang.ref.WeakReference;
52 import java.util.Random;
53 import java.util.Vector;
56 * Provides "background" audio playback capabilities, allowing the
57 * user to switch between activities without stopping playback.
59 public class MediaPlaybackService extends Service {
60 /** used to specify whether enqueue() should start playing
61 * the new list of files right away, next or once all the currently
62 * queued files have been played
64 public static final int NOW = 1;
65 public static final int NEXT = 2;
66 public static final int LAST = 3;
67 public static final int PLAYBACKSERVICE_STATUS = 1;
69 public static final int SHUFFLE_NONE = 0;
70 public static final int SHUFFLE_NORMAL = 1;
71 public static final int SHUFFLE_AUTO = 2;
73 public static final int REPEAT_NONE = 0;
74 public static final int REPEAT_CURRENT = 1;
75 public static final int REPEAT_ALL = 2;
77 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
78 public static final String META_CHANGED = "com.android.music.metachanged";
79 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
80 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
81 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
83 public static final String SERVICECMD = "com.android.music.musicservicecommand";
84 public static final String CMDNAME = "command";
85 public static final String CMDTOGGLEPAUSE = "togglepause";
86 public static final String CMDSTOP = "stop";
87 public static final String CMDPAUSE = "pause";
88 public static final String CMDPREVIOUS = "previous";
89 public static final String CMDNEXT = "next";
91 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
92 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
93 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
94 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
96 private static final int TRACK_ENDED = 1;
97 private static final int RELEASE_WAKELOCK = 2;
98 private static final int SERVER_DIED = 3;
99 private static final int FADEIN = 4;
100 private static final int MAX_HISTORY_SIZE = 100;
102 private MultiPlayer mPlayer;
103 private String mFileToPlay;
104 private int mShuffleMode = SHUFFLE_NONE;
105 private int mRepeatMode = REPEAT_NONE;
106 private int mMediaMountedCount = 0;
107 private long [] mAutoShuffleList = null;
108 private boolean mOneShot;
109 private long [] mPlayList = null;
110 private int mPlayListLen = 0;
111 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
112 private Cursor mCursor;
113 private int mPlayPos = -1;
114 private static final String LOGTAG = "MediaPlaybackService";
115 private final Shuffler mRand = new Shuffler();
116 private int mOpenFailedCounter = 0;
117 String[] mCursorCols = new String[] {
118 "audio._id AS _id", // index must match IDCOLIDX below
119 MediaStore.Audio.Media.ARTIST,
120 MediaStore.Audio.Media.ALBUM,
121 MediaStore.Audio.Media.TITLE,
122 MediaStore.Audio.Media.DATA,
123 MediaStore.Audio.Media.MIME_TYPE,
124 MediaStore.Audio.Media.ALBUM_ID,
125 MediaStore.Audio.Media.ARTIST_ID,
126 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
127 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
129 private final static int IDCOLIDX = 0;
130 private final static int PODCASTCOLIDX = 8;
131 private final static int BOOKMARKCOLIDX = 9;
132 private BroadcastReceiver mUnmountReceiver = null;
133 private WakeLock mWakeLock;
134 private int mServiceStartId = -1;
135 private boolean mServiceInUse = false;
136 private boolean mResumeAfterCall = false;
137 private boolean mIsSupposedToBePlaying = false;
138 private boolean mQuietMode = false;
140 private SharedPreferences mPreferences;
141 // We use this to distinguish between different cards when saving/restoring playlists.
142 // This will have to change if we want to support multiple simultaneous cards.
145 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
147 // interval after which we stop the service when idle
148 private static final int IDLE_DELAY = 60000;
150 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
152 public void onCallStateChanged(int state, String incomingNumber) {
153 if (state == TelephonyManager.CALL_STATE_RINGING) {
154 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
155 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
156 if (ringvolume > 0) {
157 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
160 } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
161 // pause the music while a conversation is in progress
162 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
164 } else if (state == TelephonyManager.CALL_STATE_IDLE) {
165 // start playing again
166 if (mResumeAfterCall) {
167 // resume playback only if music was playing
168 // when the call was answered
170 mResumeAfterCall = false;
176 private void startAndFadeIn() {
177 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
180 private Handler mMediaplayerHandler = new Handler() {
181 float mCurrentVolume = 1.0f;
183 public void handleMessage(Message msg) {
188 mPlayer.setVolume(mCurrentVolume);
190 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
192 mCurrentVolume += 0.01f;
193 if (mCurrentVolume < 1.0f) {
194 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
196 mCurrentVolume = 1.0f;
198 mPlayer.setVolume(mCurrentVolume);
202 if (mIsSupposedToBePlaying) {
205 // the server died when we were idle, so just
206 // reopen the same song (it will start again
207 // from the beginning though when the user
213 if (mRepeatMode == REPEAT_CURRENT) {
216 } else if (!mOneShot) {
219 notifyChange(PLAYBACK_COMPLETE);
220 mIsSupposedToBePlaying = false;
223 case RELEASE_WAKELOCK:
232 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
234 public void onReceive(Context context, Intent intent) {
235 String action = intent.getAction();
236 String cmd = intent.getStringExtra("command");
237 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
239 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
241 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
247 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
249 } else if (CMDSTOP.equals(cmd)) {
252 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
253 // Someone asked us to refresh a set of specific widgets, probably
254 // because they were just added.
255 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
256 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
261 public MediaPlaybackService() {
265 public void onCreate() {
268 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
269 mCardId = MusicUtils.getCardId(this);
271 registerExternalStorageListener();
273 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
274 mPlayer = new MultiPlayer();
275 mPlayer.setHandler(mMediaplayerHandler);
279 IntentFilter commandFilter = new IntentFilter();
280 commandFilter.addAction(SERVICECMD);
281 commandFilter.addAction(TOGGLEPAUSE_ACTION);
282 commandFilter.addAction(PAUSE_ACTION);
283 commandFilter.addAction(NEXT_ACTION);
284 commandFilter.addAction(PREVIOUS_ACTION);
285 registerReceiver(mIntentReceiver, commandFilter);
287 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
288 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
289 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
290 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
291 mWakeLock.setReferenceCounted(false);
293 // If the service was idle, but got killed before it stopped itself, the
294 // system will relaunch it. Make sure it gets stopped again in that case.
295 Message msg = mDelayedStopHandler.obtainMessage();
296 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
300 public void onDestroy() {
301 // Check that we're not being destroyed while something is still playing.
303 Log.e(LOGTAG, "Service being destroyed while still playing.");
305 // release all MediaPlayer resources, including the native player and wakelocks
309 // make sure there aren't any other messages coming
310 mDelayedStopHandler.removeCallbacksAndMessages(null);
311 mMediaplayerHandler.removeCallbacksAndMessages(null);
313 if (mCursor != null) {
318 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
319 tmgr.listen(mPhoneStateListener, 0);
321 unregisterReceiver(mIntentReceiver);
322 if (mUnmountReceiver != null) {
323 unregisterReceiver(mUnmountReceiver);
324 mUnmountReceiver = null;
330 private final char hexdigits [] = new char [] {
337 private void saveQueue(boolean full) {
341 Editor ed = mPreferences.edit();
342 //long start = System.currentTimeMillis();
344 StringBuilder q = new StringBuilder();
346 // The current playlist is saved as a list of "reverse hexadecimal"
347 // numbers, which we can generate faster than normal decimal or
348 // hexadecimal numbers, which in turn allows us to save the playlist
349 // more often without worrying too much about performance.
350 // (saving the full state takes about 40 ms under no-load conditions
352 int len = mPlayListLen;
353 for (int i = 0; i < len; i++) {
354 long n = mPlayList[i];
359 int digit = (int)(n & 0xf);
361 q.append(hexdigits[digit]);
366 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
367 ed.putString("queue", q.toString());
368 ed.putInt("cardid", mCardId);
369 if (mShuffleMode != SHUFFLE_NONE) {
370 // In shuffle mode we need to save the history too
371 len = mHistory.size();
373 for (int i = 0; i < len; i++) {
374 int n = mHistory.get(i);
379 int digit = (n & 0xf);
381 q.append(hexdigits[digit]);
386 ed.putString("history", q.toString());
389 ed.putInt("curpos", mPlayPos);
390 if (mPlayer.isInitialized()) {
391 ed.putLong("seekpos", mPlayer.position());
393 ed.putInt("repeatmode", mRepeatMode);
394 ed.putInt("shufflemode", mShuffleMode);
397 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
400 private void reloadQueue() {
403 boolean newstyle = false;
405 if (mPreferences.contains("cardid")) {
407 id = mPreferences.getInt("cardid", ~mCardId);
410 // Only restore the saved playlist if the card is still
411 // the same one as when the playlist was saved
412 q = mPreferences.getString("queue", "");
414 int qlen = q != null ? q.length() : 0;
416 //Log.i("@@@@ service", "loaded queue: " + q);
420 for (int i = 0; i < qlen; i++) {
421 char c = q.charAt(i);
423 ensurePlayListCapacity(plen + 1);
429 if (c >= '0' && c <= '9') {
430 n += ((c - '0') << shift);
431 } else if (c >= 'a' && c <= 'f') {
432 n += ((10 + c - 'a') << shift);
434 // bogus playlist data
443 int pos = mPreferences.getInt("curpos", 0);
444 if (pos < 0 || pos >= mPlayListLen) {
445 // The saved playlist is bogus, discard it
451 // When reloadQueue is called in response to a card-insertion,
452 // we might not be able to query the media provider right away.
453 // To deal with this, try querying for the current file, and if
454 // that fails, wait a while and try again. If that too fails,
455 // assume there is a problem and don't restore the state.
456 Cursor crsr = MusicUtils.query(this,
457 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
458 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
459 if (crsr == null || crsr.getCount() == 0) {
460 // wait a bit and try again
461 SystemClock.sleep(3000);
462 crsr = getContentResolver().query(
463 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
464 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
470 // Make sure we don't auto-skip to the next song, since that
471 // also starts playback. What could happen in that case is:
473 // - go to UMS and delete some files, including the currently playing one
474 // - come back from UMS
476 // - music app is killed for some reason (out of memory)
477 // - music service is restarted, service restores state, doesn't find
478 // the "current" file, goes to the next and: playback starts on its
479 // own, potentially at some random inconvenient time.
480 mOpenFailedCounter = 20;
484 if (!mPlayer.isInitialized()) {
485 // couldn't restore the saved state
490 long seekpos = mPreferences.getLong("seekpos", 0);
491 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
492 Log.d(LOGTAG, "restored queue, currently at position "
493 + position() + "/" + duration()
494 + " (requested " + seekpos + ")");
496 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
497 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
498 repmode = REPEAT_NONE;
500 mRepeatMode = repmode;
502 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
503 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
504 shufmode = SHUFFLE_NONE;
506 if (shufmode != SHUFFLE_NONE) {
507 // in shuffle mode we need to restore the history too
508 q = mPreferences.getString("history", "");
509 qlen = q != null ? q.length() : 0;
515 for (int i = 0; i < qlen; i++) {
516 char c = q.charAt(i);
518 if (n >= mPlayListLen) {
519 // bogus history data
527 if (c >= '0' && c <= '9') {
528 n += ((c - '0') << shift);
529 } else if (c >= 'a' && c <= 'f') {
530 n += ((10 + c - 'a') << shift);
532 // bogus history data
541 if (shufmode == SHUFFLE_AUTO) {
542 if (! makeAutoShuffleList()) {
543 shufmode = SHUFFLE_NONE;
546 mShuffleMode = shufmode;
551 public IBinder onBind(Intent intent) {
552 mDelayedStopHandler.removeCallbacksAndMessages(null);
553 mServiceInUse = true;
558 public void onRebind(Intent intent) {
559 mDelayedStopHandler.removeCallbacksAndMessages(null);
560 mServiceInUse = true;
564 public int onStartCommand(Intent intent, int flags, int startId) {
565 mServiceStartId = startId;
566 mDelayedStopHandler.removeCallbacksAndMessages(null);
568 if (intent != null) {
569 String action = intent.getAction();
570 String cmd = intent.getStringExtra("command");
572 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
574 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
575 if (position() < 2000) {
581 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
587 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
589 } else if (CMDSTOP.equals(cmd)) {
595 // make sure the service will shut down on its own if it was
596 // just started but not bound to and nothing is playing
597 mDelayedStopHandler.removeCallbacksAndMessages(null);
598 Message msg = mDelayedStopHandler.obtainMessage();
599 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
604 public boolean onUnbind(Intent intent) {
605 mServiceInUse = false;
607 // Take a snapshot of the current playlist
610 if (isPlaying() || mResumeAfterCall) {
611 // something is currently playing, or will be playing once
612 // an in-progress call ends, so don't stop the service now.
616 // If there is a playlist but playback is paused, then wait a while
617 // before stopping the service, so that pause/resume isn't slow.
618 // Also delay stopping the service if we're transitioning between tracks.
619 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
620 Message msg = mDelayedStopHandler.obtainMessage();
621 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
625 // No active playlist, OK to stop the service right now
626 stopSelf(mServiceStartId);
630 private Handler mDelayedStopHandler = new Handler() {
632 public void handleMessage(Message msg) {
633 // Check again to make sure nothing is playing right now
634 if (isPlaying() || mResumeAfterCall || mServiceInUse
635 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
638 // save the queue again, because it might have changed
639 // since the user exited the music app (because of
640 // party-shuffle or because the play-position changed)
642 stopSelf(mServiceStartId);
647 * Called when we receive a ACTION_MEDIA_EJECT notification.
649 * @param storagePath path to mount point for the removed media
651 public void closeExternalStorageFiles(String storagePath) {
652 // stop playback and clean up if the SD card is going to be unmounted.
654 notifyChange(QUEUE_CHANGED);
655 notifyChange(META_CHANGED);
659 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
660 * The intent will call closeExternalStorageFiles() if the external media
661 * is going to be ejected, so applications can clean up any files they have open.
663 public void registerExternalStorageListener() {
664 if (mUnmountReceiver == null) {
665 mUnmountReceiver = new BroadcastReceiver() {
667 public void onReceive(Context context, Intent intent) {
668 String action = intent.getAction();
669 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
671 mOneShot = true; // This makes us not save the state again later,
672 // which would be wrong because the song ids and
673 // card id might not match.
674 closeExternalStorageFiles(intent.getData().getPath());
675 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
676 mMediaMountedCount++;
677 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
679 notifyChange(QUEUE_CHANGED);
680 notifyChange(META_CHANGED);
684 IntentFilter iFilter = new IntentFilter();
685 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
686 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
687 iFilter.addDataScheme("file");
688 registerReceiver(mUnmountReceiver, iFilter);
693 * Notify the change-receivers that something has changed.
694 * The intent that is sent contains the following data
695 * for the currently playing track:
696 * "id" - Integer: the database row ID
697 * "artist" - String: the name of the artist
698 * "album" - String: the name of the album
699 * "track" - String: the name of the track
700 * The intent has an action that is one of
701 * "com.android.music.metachanged"
702 * "com.android.music.queuechanged",
703 * "com.android.music.playbackcomplete"
704 * "com.android.music.playstatechanged"
705 * respectively indicating that a new track has
706 * started playing, that the playback queue has
707 * changed, that playback has stopped because
708 * the last file in the list has been played,
709 * or that the play-state changed (paused/resumed).
711 private void notifyChange(String what) {
713 Intent i = new Intent(what);
714 i.putExtra("id", Long.valueOf(getAudioId()));
715 i.putExtra("artist", getArtistName());
716 i.putExtra("album",getAlbumName());
717 i.putExtra("track", getTrackName());
720 if (what.equals(QUEUE_CHANGED)) {
726 // Share this notification directly with our widgets
727 mAppWidgetProvider.notifyChange(this, what);
730 private void ensurePlayListCapacity(int size) {
731 if (mPlayList == null || size > mPlayList.length) {
732 // reallocate at 2x requested size so we don't
733 // need to grow and copy the array for every
735 long [] newlist = new long[size * 2];
736 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
737 for (int i = 0; i < len; i++) {
738 newlist[i] = mPlayList[i];
742 // FIXME: shrink the array when the needed size is much smaller
743 // than the allocated size
746 // insert the list of songs at the specified position in the playlist
747 private void addToPlayList(long [] list, int position) {
748 int addlen = list.length;
749 if (position < 0) { // overwrite
753 ensurePlayListCapacity(mPlayListLen + addlen);
754 if (position > mPlayListLen) {
755 position = mPlayListLen;
758 // move part of list after insertion point
759 int tailsize = mPlayListLen - position;
760 for (int i = tailsize ; i > 0 ; i--) {
761 mPlayList[position + i] = mPlayList[position + i - addlen];
764 // copy list into playlist
765 for (int i = 0; i < addlen; i++) {
766 mPlayList[position + i] = list[i];
768 mPlayListLen += addlen;
772 * Appends a list of tracks to the current playlist.
773 * If nothing is playing currently, playback will be started at
775 * If the action is NOW, playback will switch to the first of
776 * the new tracks immediately.
777 * @param list The list of tracks to append.
778 * @param action NOW, NEXT or LAST
780 public void enqueue(long [] list, int action) {
782 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
783 addToPlayList(list, mPlayPos + 1);
784 notifyChange(QUEUE_CHANGED);
786 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
787 addToPlayList(list, Integer.MAX_VALUE);
788 notifyChange(QUEUE_CHANGED);
790 mPlayPos = mPlayListLen - list.length;
793 notifyChange(META_CHANGED);
801 notifyChange(META_CHANGED);
807 * Replaces the current playlist with a new list,
808 * and prepares for starting playback at the specified
809 * position in the list, or a random position if the
810 * specified position is 0.
811 * @param list The new list of tracks.
813 public void open(long [] list, int position) {
814 synchronized (this) {
815 if (mShuffleMode == SHUFFLE_AUTO) {
816 mShuffleMode = SHUFFLE_NORMAL;
818 long oldId = getAudioId();
819 int listlength = list.length;
820 boolean newlist = true;
821 if (mPlayListLen == listlength) {
822 // possible fast path: list might be the same
824 for (int i = 0; i < listlength; i++) {
825 if (list[i] != mPlayList[i]) {
832 addToPlayList(list, -1);
833 notifyChange(QUEUE_CHANGED);
835 int oldpos = mPlayPos;
839 mPlayPos = mRand.nextInt(mPlayListLen);
843 saveBookmarkIfNeeded();
845 if (oldId != getAudioId()) {
846 notifyChange(META_CHANGED);
852 * Moves the item at index1 to index2.
856 public void moveQueueItem(int index1, int index2) {
857 synchronized (this) {
858 if (index1 >= mPlayListLen) {
859 index1 = mPlayListLen - 1;
861 if (index2 >= mPlayListLen) {
862 index2 = mPlayListLen - 1;
864 if (index1 < index2) {
865 long tmp = mPlayList[index1];
866 for (int i = index1; i < index2; i++) {
867 mPlayList[i] = mPlayList[i+1];
869 mPlayList[index2] = tmp;
870 if (mPlayPos == index1) {
872 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
875 } else if (index2 < index1) {
876 long tmp = mPlayList[index1];
877 for (int i = index1; i > index2; i--) {
878 mPlayList[i] = mPlayList[i-1];
880 mPlayList[index2] = tmp;
881 if (mPlayPos == index1) {
883 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
887 notifyChange(QUEUE_CHANGED);
892 * Returns the current play list
893 * @return An array of integers containing the IDs of the tracks in the play list
895 public long [] getQueue() {
896 synchronized (this) {
897 int len = mPlayListLen;
898 long [] list = new long[len];
899 for (int i = 0; i < len; i++) {
900 list[i] = mPlayList[i];
906 private void openCurrent() {
907 synchronized (this) {
908 if (mCursor != null) {
912 if (mPlayListLen == 0) {
917 String id = String.valueOf(mPlayList[mPlayPos]);
919 mCursor = getContentResolver().query(
920 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
921 mCursorCols, "_id=" + id , null, null);
922 if (mCursor != null) {
923 mCursor.moveToFirst();
924 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
925 // go to bookmark if needed
927 long bookmark = getBookmark();
928 // Start playing a little bit before the bookmark,
929 // so it's easier to get back in to the narrative.
930 seek(bookmark - 5000);
936 public void openAsync(String path) {
937 synchronized (this) {
942 mRepeatMode = REPEAT_NONE;
943 ensurePlayListCapacity(1);
949 mPlayer.setDataSourceAsync(mFileToPlay);
955 * Opens the specified file and readies it for playback.
957 * @param path The full path of the file to be opened.
958 * @param oneshot when set to true, playback will stop after this file completes, instead
959 * of moving on to the next track in the list
961 public void open(String path, boolean oneshot) {
962 synchronized (this) {
968 mRepeatMode = REPEAT_NONE;
969 ensurePlayListCapacity(1);
974 // if mCursor is null, try to associate path with a database cursor
975 if (mCursor == null) {
977 ContentResolver resolver = getContentResolver();
980 String selectionArgs[];
981 if (path.startsWith("content://media/")) {
982 uri = Uri.parse(path);
984 selectionArgs = null;
986 uri = MediaStore.Audio.Media.getContentUriForPath(path);
987 where = MediaStore.Audio.Media.DATA + "=?";
988 selectionArgs = new String[] { path };
992 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
993 if (mCursor != null) {
994 if (mCursor.getCount() == 0) {
998 mCursor.moveToNext();
999 ensurePlayListCapacity(1);
1001 mPlayList[0] = mCursor.getLong(IDCOLIDX);
1005 } catch (UnsupportedOperationException ex) {
1009 mPlayer.setDataSource(mFileToPlay);
1011 if (! mPlayer.isInitialized()) {
1013 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1014 // beware: this ends up being recursive because next() calls open() again.
1017 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1018 // need to make sure we only shows this once
1019 mOpenFailedCounter = 0;
1021 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1023 Log.d(LOGTAG, "Failed to open file for playback");
1026 mOpenFailedCounter = 0;
1032 * Starts playback of a previously opened file.
1034 public void play() {
1035 if (mPlayer.isInitialized()) {
1036 // if we are at the end of the song, go to the next song first
1037 long duration = mPlayer.duration();
1038 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1039 mPlayer.position() >= duration - 2000) {
1045 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1046 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1047 if (getAudioId() < 0) {
1049 views.setTextViewText(R.id.trackname, getPath());
1050 views.setTextViewText(R.id.artistalbum, null);
1052 String artist = getArtistName();
1053 views.setTextViewText(R.id.trackname, getTrackName());
1054 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
1055 artist = getString(R.string.unknown_artist_name);
1057 String album = getAlbumName();
1058 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
1059 album = getString(R.string.unknown_album_name);
1062 views.setTextViewText(R.id.artistalbum,
1063 getString(R.string.notification_artist_album, artist, album)
1067 Notification status = new Notification();
1068 status.contentView = views;
1069 status.flags |= Notification.FLAG_ONGOING_EVENT;
1070 status.icon = R.drawable.stat_notify_musicplayer;
1071 status.contentIntent = PendingIntent.getActivity(this, 0,
1072 new Intent("com.android.music.PLAYBACK_VIEWER")
1073 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
1074 startForeground(PLAYBACKSERVICE_STATUS, status);
1075 if (!mIsSupposedToBePlaying) {
1076 mIsSupposedToBePlaying = true;
1077 notifyChange(PLAYSTATE_CHANGED);
1080 } else if (mPlayListLen <= 0) {
1081 // This is mostly so that if you press 'play' on a bluetooth headset
1082 // without every having played anything before, it will still play
1084 setShuffleMode(SHUFFLE_AUTO);
1088 private void stop(boolean remove_status_icon) {
1089 if (mPlayer.isInitialized()) {
1093 if (mCursor != null) {
1097 if (remove_status_icon) {
1100 stopForeground(false);
1102 if (remove_status_icon) {
1103 mIsSupposedToBePlaying = false;
1110 public void stop() {
1115 * Pauses playback (call play() to resume)
1117 public void pause() {
1118 synchronized(this) {
1122 mIsSupposedToBePlaying = false;
1123 notifyChange(PLAYSTATE_CHANGED);
1124 saveBookmarkIfNeeded();
1129 /** Returns whether something is currently playing
1131 * @return true if something is playing (or will be playing shortly, in case
1132 * we're currently transitioning between tracks), false if not.
1134 public boolean isPlaying() {
1135 return mIsSupposedToBePlaying;
1139 Desired behavior for prev/next/shuffle:
1141 - NEXT will move to the next track in the list when not shuffling, and to
1142 a track randomly picked from the not-yet-played tracks when shuffling.
1143 If all tracks have already been played, pick from the full set, but
1144 avoid picking the previously played track if possible.
1145 - when shuffling, PREV will go to the previously played track. Hitting PREV
1146 again will go to the track played before that, etc. When the start of the
1147 history has been reached, PREV is a no-op.
1148 When not shuffling, PREV will go to the sequentially previous track (the
1149 difference with the shuffle-case is mainly that when not shuffling, the
1150 user can back up to tracks that are not in the history).
1153 When playing an album with 10 tracks from the start, and enabling shuffle
1154 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1155 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1156 When hitting 'prev' 8 times while playing track 7 in this example, the
1157 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1158 a random track will be picked again. If at any time user disables shuffling
1159 the next/previous track will be picked in sequential order again.
1162 public void prev() {
1163 synchronized (this) {
1165 // we were playing a specific file not part of a playlist, so there is no 'previous'
1170 if (mShuffleMode == SHUFFLE_NORMAL) {
1171 // go to previously-played track and remove it from the history
1172 int histsize = mHistory.size();
1173 if (histsize == 0) {
1177 Integer pos = mHistory.remove(histsize - 1);
1178 mPlayPos = pos.intValue();
1183 mPlayPos = mPlayListLen - 1;
1186 saveBookmarkIfNeeded();
1190 notifyChange(META_CHANGED);
1194 public void next(boolean force) {
1195 synchronized (this) {
1197 // we were playing a specific file not part of a playlist, so there is no 'next'
1203 if (mPlayListLen <= 0) {
1204 Log.d(LOGTAG, "No play queue");
1208 // Store the current file in the history, but keep the history at a
1210 if (mPlayPos >= 0) {
1211 mHistory.add(Integer.valueOf(mPlayPos));
1213 if (mHistory.size() > MAX_HISTORY_SIZE) {
1214 mHistory.removeElementAt(0);
1217 if (mShuffleMode == SHUFFLE_NORMAL) {
1218 // Pick random next track from the not-yet-played ones
1219 // TODO: make it work right after adding/removing items in the queue.
1221 int numTracks = mPlayListLen;
1222 int[] tracks = new int[numTracks];
1223 for (int i=0;i < numTracks; i++) {
1227 int numHistory = mHistory.size();
1228 int numUnplayed = numTracks;
1229 for (int i=0;i < numHistory; i++) {
1230 int idx = mHistory.get(i).intValue();
1231 if (idx < numTracks && tracks[idx] >= 0) {
1237 // 'numUnplayed' now indicates how many tracks have not yet
1238 // been played, and 'tracks' contains the indices of those
1240 if (numUnplayed <=0) {
1241 // everything's already been played
1242 if (mRepeatMode == REPEAT_ALL || force) {
1243 //pick from full set
1244 numUnplayed = numTracks;
1245 for (int i=0;i < numTracks; i++) {
1254 int skip = mRand.nextInt(numUnplayed);
1257 while (tracks[++cnt] < 0)
1265 } else if (mShuffleMode == SHUFFLE_AUTO) {
1266 doAutoShuffleUpdate();
1269 if (mPlayPos >= mPlayListLen - 1) {
1270 // we're at the end of the list
1271 if (mRepeatMode == REPEAT_NONE && !force) {
1274 notifyChange(PLAYBACK_COMPLETE);
1275 mIsSupposedToBePlaying = false;
1277 } else if (mRepeatMode == REPEAT_ALL || force) {
1284 saveBookmarkIfNeeded();
1288 notifyChange(META_CHANGED);
1292 private void gotoIdleState() {
1293 mDelayedStopHandler.removeCallbacksAndMessages(null);
1294 Message msg = mDelayedStopHandler.obtainMessage();
1295 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1296 stopForeground(true);
1299 private void saveBookmarkIfNeeded() {
1302 long pos = position();
1303 long bookmark = getBookmark();
1304 long duration = duration();
1305 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1306 (pos > bookmark && (pos - 10000) < bookmark)) {
1307 // The existing bookmark is close to the current
1308 // position, so don't update it.
1311 if (pos < 15000 || (pos + 10000) > duration) {
1312 // if we're near the start or end, clear the bookmark
1316 // write 'pos' to the bookmark field
1317 ContentValues values = new ContentValues();
1318 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1319 Uri uri = ContentUris.withAppendedId(
1320 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1321 getContentResolver().update(uri, values, null, null);
1323 } catch (SQLiteException ex) {
1327 // Make sure there are at least 5 items after the currently playing item
1328 // and no more than 10 items before.
1329 private void doAutoShuffleUpdate() {
1330 boolean notify = false;
1331 // remove old entries
1332 if (mPlayPos > 10) {
1333 removeTracks(0, mPlayPos - 9);
1336 // add new entries if needed
1337 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1338 for (int i = 0; i < to_add; i++) {
1339 // pick something at random from the list
1340 int idx = mRand.nextInt(mAutoShuffleList.length);
1341 long which = mAutoShuffleList[idx];
1342 ensurePlayListCapacity(mPlayListLen + 1);
1343 mPlayList[mPlayListLen++] = which;
1347 notifyChange(QUEUE_CHANGED);
1351 // A simple variation of Random that makes sure that the
1352 // value it returns is not equal to the value it returned
1353 // previously, unless the interval is 1.
1354 private static class Shuffler {
1355 private int mPrevious;
1356 private Random mRandom = new Random();
1357 public int nextInt(int interval) {
1360 ret = mRandom.nextInt(interval);
1361 } while (ret == mPrevious && interval > 1);
1367 private boolean makeAutoShuffleList() {
1368 ContentResolver res = getContentResolver();
1371 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1372 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1374 if (c == null || c.getCount() == 0) {
1377 int len = c.getCount();
1378 long [] list = new long[len];
1379 for (int i = 0; i < len; i++) {
1381 list[i] = c.getLong(0);
1383 mAutoShuffleList = list;
1385 } catch (RuntimeException ex) {
1395 * Removes the range of tracks specified from the play list. If a file within the range is
1396 * the file currently being played, playback will move to the next file after the
1398 * @param first The first file to be removed
1399 * @param last The last file to be removed
1400 * @return the number of tracks deleted
1402 public int removeTracks(int first, int last) {
1403 int numremoved = removeTracksInternal(first, last);
1404 if (numremoved > 0) {
1405 notifyChange(QUEUE_CHANGED);
1410 private int removeTracksInternal(int first, int last) {
1411 synchronized (this) {
1412 if (last < first) return 0;
1413 if (first < 0) first = 0;
1414 if (last >= mPlayListLen) last = mPlayListLen - 1;
1416 boolean gotonext = false;
1417 if (first <= mPlayPos && mPlayPos <= last) {
1420 } else if (mPlayPos > last) {
1421 mPlayPos -= (last - first + 1);
1423 int num = mPlayListLen - last - 1;
1424 for (int i = 0; i < num; i++) {
1425 mPlayList[first + i] = mPlayList[last + 1 + i];
1427 mPlayListLen -= last - first + 1;
1430 if (mPlayListLen == 0) {
1434 if (mPlayPos >= mPlayListLen) {
1437 boolean wasPlaying = isPlaying();
1445 return last - first + 1;
1450 * Removes all instances of the track with the given id
1451 * from the playlist.
1452 * @param id The id to be removed
1453 * @return how many instances of the track were removed
1455 public int removeTrack(long id) {
1457 synchronized (this) {
1458 for (int i = 0; i < mPlayListLen; i++) {
1459 if (mPlayList[i] == id) {
1460 numremoved += removeTracksInternal(i, i);
1465 if (numremoved > 0) {
1466 notifyChange(QUEUE_CHANGED);
1471 public void setShuffleMode(int shufflemode) {
1472 synchronized(this) {
1473 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1476 mShuffleMode = shufflemode;
1477 if (mShuffleMode == SHUFFLE_AUTO) {
1478 if (makeAutoShuffleList()) {
1480 doAutoShuffleUpdate();
1484 notifyChange(META_CHANGED);
1487 // failed to build a list of files to shuffle
1488 mShuffleMode = SHUFFLE_NONE;
1494 public int getShuffleMode() {
1495 return mShuffleMode;
1498 public void setRepeatMode(int repeatmode) {
1499 synchronized(this) {
1500 mRepeatMode = repeatmode;
1504 public int getRepeatMode() {
1508 public int getMediaMountedCount() {
1509 return mMediaMountedCount;
1513 * Returns the path of the currently playing file, or null if
1514 * no file is currently playing.
1516 public String getPath() {
1521 * Returns the rowid of the currently playing file, or -1 if
1522 * no file is currently playing.
1524 public long getAudioId() {
1525 synchronized (this) {
1526 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1527 return mPlayList[mPlayPos];
1534 * Returns the position in the queue
1535 * @return the position in the queue
1537 public int getQueuePosition() {
1538 synchronized(this) {
1544 * Starts playing the track at the given position in the queue.
1545 * @param pos The position in the queue of the track that will be played.
1547 public void setQueuePosition(int pos) {
1548 synchronized(this) {
1553 notifyChange(META_CHANGED);
1554 if (mShuffleMode == SHUFFLE_AUTO) {
1555 doAutoShuffleUpdate();
1560 public String getArtistName() {
1561 synchronized(this) {
1562 if (mCursor == null) {
1565 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1569 public long getArtistId() {
1570 synchronized (this) {
1571 if (mCursor == null) {
1574 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
1578 public String getAlbumName() {
1579 synchronized (this) {
1580 if (mCursor == null) {
1583 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1587 public long getAlbumId() {
1588 synchronized (this) {
1589 if (mCursor == null) {
1592 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
1596 public String getTrackName() {
1597 synchronized (this) {
1598 if (mCursor == null) {
1601 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1605 private boolean isPodcast() {
1606 synchronized (this) {
1607 if (mCursor == null) {
1610 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1614 private long getBookmark() {
1615 synchronized (this) {
1616 if (mCursor == null) {
1619 return mCursor.getLong(BOOKMARKCOLIDX);
1624 * Returns the duration of the file in milliseconds.
1625 * Currently this method returns -1 for the duration of MIDI files.
1627 public long duration() {
1628 if (mPlayer.isInitialized()) {
1629 return mPlayer.duration();
1635 * Returns the current playback position in milliseconds
1637 public long position() {
1638 if (mPlayer.isInitialized()) {
1639 return mPlayer.position();
1645 * Seeks to the position specified.
1647 * @param pos The position to seek to, in milliseconds
1649 public long seek(long pos) {
1650 if (mPlayer.isInitialized()) {
1651 if (pos < 0) pos = 0;
1652 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1653 return mPlayer.seek(pos);
1659 * Provides a unified interface for dealing with midi files and
1660 * other media files.
1662 private class MultiPlayer {
1663 private MediaPlayer mMediaPlayer = new MediaPlayer();
1664 private Handler mHandler;
1665 private boolean mIsInitialized = false;
1667 public MultiPlayer() {
1668 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1671 public void setDataSourceAsync(String path) {
1673 mMediaPlayer.reset();
1674 mMediaPlayer.setDataSource(path);
1675 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1676 mMediaPlayer.setOnPreparedListener(preparedlistener);
1677 mMediaPlayer.prepareAsync();
1678 } catch (IOException ex) {
1679 // TODO: notify the user why the file couldn't be opened
1680 mIsInitialized = false;
1682 } catch (IllegalArgumentException ex) {
1683 // TODO: notify the user why the file couldn't be opened
1684 mIsInitialized = false;
1687 mMediaPlayer.setOnCompletionListener(listener);
1688 mMediaPlayer.setOnErrorListener(errorListener);
1690 mIsInitialized = true;
1693 public void setDataSource(String path) {
1695 mMediaPlayer.reset();
1696 mMediaPlayer.setOnPreparedListener(null);
1697 if (path.startsWith("content://")) {
1698 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1700 mMediaPlayer.setDataSource(path);
1702 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1703 mMediaPlayer.prepare();
1704 } catch (IOException ex) {
1705 // TODO: notify the user why the file couldn't be opened
1706 mIsInitialized = false;
1708 } catch (IllegalArgumentException ex) {
1709 // TODO: notify the user why the file couldn't be opened
1710 mIsInitialized = false;
1713 mMediaPlayer.setOnCompletionListener(listener);
1714 mMediaPlayer.setOnErrorListener(errorListener);
1716 mIsInitialized = true;
1719 public boolean isInitialized() {
1720 return mIsInitialized;
1723 public void start() {
1724 mMediaPlayer.start();
1727 public void stop() {
1728 mMediaPlayer.reset();
1729 mIsInitialized = false;
1733 * You CANNOT use this player anymore after calling release()
1735 public void release() {
1737 mMediaPlayer.release();
1740 public void pause() {
1741 mMediaPlayer.pause();
1744 public void setHandler(Handler handler) {
1748 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1749 public void onCompletion(MediaPlayer mp) {
1750 // Acquire a temporary wakelock, since when we return from
1751 // this callback the MediaPlayer will release its wakelock
1752 // and allow the device to go to sleep.
1753 // This temporary wakelock is released when the RELEASE_WAKELOCK
1754 // message is processed, but just in case, put a timeout on it.
1755 mWakeLock.acquire(30000);
1756 mHandler.sendEmptyMessage(TRACK_ENDED);
1757 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1761 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1762 public void onPrepared(MediaPlayer mp) {
1763 notifyChange(ASYNC_OPEN_COMPLETE);
1767 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1768 public boolean onError(MediaPlayer mp, int what, int extra) {
1770 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1771 mIsInitialized = false;
1772 mMediaPlayer.release();
1773 // Creating a new MediaPlayer and settings its wakemode does not
1774 // require the media service, so it's OK to do this now, while the
1775 // service is still being restarted
1776 mMediaPlayer = new MediaPlayer();
1777 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1778 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1781 Log.d("MultiPlayer", "Error: " + what + "," + extra);
1788 public long duration() {
1789 return mMediaPlayer.getDuration();
1792 public long position() {
1793 return mMediaPlayer.getCurrentPosition();
1796 public long seek(long whereto) {
1797 mMediaPlayer.seekTo((int) whereto);
1801 public void setVolume(float vol) {
1802 mMediaPlayer.setVolume(vol, vol);
1807 * By making this a static class with a WeakReference to the Service, we
1808 * ensure that the Service can be GCd even when the system process still
1809 * has a remote reference to the stub.
1811 static class ServiceStub extends IMediaPlaybackService.Stub {
1812 WeakReference<MediaPlaybackService> mService;
1814 ServiceStub(MediaPlaybackService service) {
1815 mService = new WeakReference<MediaPlaybackService>(service);
1818 public void openFileAsync(String path)
1820 mService.get().openAsync(path);
1822 public void openFile(String path, boolean oneShot)
1824 mService.get().open(path, oneShot);
1826 public void open(long [] list, int position) {
1827 mService.get().open(list, position);
1829 public int getQueuePosition() {
1830 return mService.get().getQueuePosition();
1832 public void setQueuePosition(int index) {
1833 mService.get().setQueuePosition(index);
1835 public boolean isPlaying() {
1836 return mService.get().isPlaying();
1838 public void stop() {
1839 mService.get().stop();
1841 public void pause() {
1842 mService.get().pause();
1844 public void play() {
1845 mService.get().play();
1847 public void prev() {
1848 mService.get().prev();
1850 public void next() {
1851 mService.get().next(true);
1853 public String getTrackName() {
1854 return mService.get().getTrackName();
1856 public String getAlbumName() {
1857 return mService.get().getAlbumName();
1859 public long getAlbumId() {
1860 return mService.get().getAlbumId();
1862 public String getArtistName() {
1863 return mService.get().getArtistName();
1865 public long getArtistId() {
1866 return mService.get().getArtistId();
1868 public void enqueue(long [] list , int action) {
1869 mService.get().enqueue(list, action);
1871 public long [] getQueue() {
1872 return mService.get().getQueue();
1874 public void moveQueueItem(int from, int to) {
1875 mService.get().moveQueueItem(from, to);
1877 public String getPath() {
1878 return mService.get().getPath();
1880 public long getAudioId() {
1881 return mService.get().getAudioId();
1883 public long position() {
1884 return mService.get().position();
1886 public long duration() {
1887 return mService.get().duration();
1889 public long seek(long pos) {
1890 return mService.get().seek(pos);
1892 public void setShuffleMode(int shufflemode) {
1893 mService.get().setShuffleMode(shufflemode);
1895 public int getShuffleMode() {
1896 return mService.get().getShuffleMode();
1898 public int removeTracks(int first, int last) {
1899 return mService.get().removeTracks(first, last);
1901 public int removeTrack(long id) {
1902 return mService.get().removeTrack(id);
1904 public void setRepeatMode(int repeatmode) {
1905 mService.get().setRepeatMode(repeatmode);
1907 public int getRepeatMode() {
1908 return mService.get().getRepeatMode();
1910 public int getMediaMountedCount() {
1911 return mService.get().getMediaMountedCount();
1916 private final IBinder mBinder = new ServiceStub(this);