2 * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3 * (the "License"); you may not use this file except in compliance with the
4 * License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
12 package com.cyngn.eleven;
14 import android.annotation.SuppressLint;
15 import android.app.AlarmManager;
16 import android.app.PendingIntent;
17 import android.app.Service;
18 import android.appwidget.AppWidgetManager;
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.SharedPreferences;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.media.AudioManager;
29 import android.media.AudioManager.OnAudioFocusChangeListener;
30 import android.media.MediaMetadataRetriever;
31 import android.media.MediaPlayer;
32 import android.media.MediaPlayer.OnCompletionListener;
33 import android.media.RemoteControlClient;
34 import android.media.audiofx.AudioEffect;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.IBinder;
39 import android.os.Looper;
40 import android.os.Message;
41 import android.os.PowerManager;
42 import android.os.PowerManager.WakeLock;
43 import android.os.RemoteException;
44 import android.os.SystemClock;
45 import android.provider.MediaStore;
46 import android.provider.MediaStore.Audio.AlbumColumns;
47 import android.provider.MediaStore.Audio.AudioColumns;
48 import android.util.Log;
50 import com.cyngn.eleven.appwidgets.AppWidgetLarge;
51 import com.cyngn.eleven.appwidgets.AppWidgetLargeAlternate;
52 import com.cyngn.eleven.appwidgets.AppWidgetSmall;
53 import com.cyngn.eleven.cache.ImageCache;
54 import com.cyngn.eleven.cache.ImageFetcher;
55 import com.cyngn.eleven.provider.RecentStore;
56 import com.cyngn.eleven.provider.SongPlayCount;
57 import com.cyngn.eleven.utils.ApolloUtils;
58 import com.cyngn.eleven.utils.Lists;
59 import com.cyngn.eleven.utils.MusicUtils;
60 import com.cyngn.eleven.utils.PreferenceUtils;
62 import java.io.IOException;
63 import java.lang.ref.WeakReference;
64 import java.util.LinkedList;
65 import java.util.Random;
66 import java.util.TreeSet;
69 * A backbround {@link Service} used to keep music playing between activities
70 * and when the user moves Apollo into the background.
72 @SuppressLint("NewApi")
73 public class MusicPlaybackService extends Service {
74 private static final String TAG = "MusicPlaybackService";
75 private static final boolean D = false;
78 * Indicates that the music has paused or resumed
80 public static final String PLAYSTATE_CHANGED = "com.cyngn.eleven.playstatechanged";
83 * Indicates that music playback position within
86 public static final String POSITION_CHANGED = "com.cyngn.eleven.positionchanged";
89 * Indicates the meta data has changed in some way, like a track change
91 public static final String META_CHANGED = "com.cyngn.eleven.metachanged";
94 * Indicates the queue has been updated
96 public static final String QUEUE_CHANGED = "com.cyngn.eleven.queuechanged";
99 * Indicates the queue has been updated
101 public static final String PLAYLIST_CHANGED = "com.cyngn.eleven.playlistchanged";
104 * Indicates the repeat mode chaned
106 public static final String REPEATMODE_CHANGED = "com.cyngn.eleven.repeatmodechanged";
109 * Indicates the shuffle mode chaned
111 public static final String SHUFFLEMODE_CHANGED = "com.cyngn.eleven.shufflemodechanged";
114 * For backwards compatibility reasons, also provide sticky
115 * broadcasts under the music package
117 public static final String ELEVEN_PACKAGE_NAME = "com.cyngn.eleven";
118 public static final String MUSIC_PACKAGE_NAME = "com.android.music";
121 * Called to indicate a general service commmand. Used in
122 * {@link MediaButtonIntentReceiver}
124 public static final String SERVICECMD = "com.cyngn.eleven.musicservicecommand";
127 * Called to go toggle between pausing and playing the music
129 public static final String TOGGLEPAUSE_ACTION = "com.cyngn.eleven.togglepause";
132 * Called to go to pause the playback
134 public static final String PAUSE_ACTION = "com.cyngn.eleven.pause";
137 * Called to go to stop the playback
139 public static final String STOP_ACTION = "com.cyngn.eleven.stop";
142 * Called to go to the previous track or the beginning of the track if partway through the track
144 public static final String PREVIOUS_ACTION = "com.cyngn.eleven.previous";
147 * Called to go to the previous track regardless of how far in the current track the playback is
149 public static final String PREVIOUS_FORCE_ACTION = "com.cyngn.eleven.previous.force";
152 * Called to go to the next track
154 public static final String NEXT_ACTION = "com.cyngn.eleven.next";
157 * Called to change the repeat mode
159 public static final String REPEAT_ACTION = "com.cyngn.eleven.repeat";
162 * Called to change the shuffle mode
164 public static final String SHUFFLE_ACTION = "com.cyngn.eleven.shuffle";
167 * Called to update the service about the foreground state of Apollo's activities
169 public static final String FOREGROUND_STATE_CHANGED = "com.cyngn.eleven.fgstatechanged";
171 public static final String NOW_IN_FOREGROUND = "nowinforeground";
173 public static final String FROM_MEDIA_BUTTON = "frommediabutton";
176 * Used to easily notify a list that it should refresh. i.e. A playlist
179 public static final String REFRESH = "com.cyngn.eleven.refresh";
182 * Used by the alarm intent to shutdown the service after being idle
184 private static final String SHUTDOWN = "com.cyngn.eleven.shutdown";
187 * Called to update the remote control client
189 public static final String UPDATE_LOCKSCREEN = "com.cyngn.eleven.updatelockscreen";
191 public static final String CMDNAME = "command";
193 public static final String CMDTOGGLEPAUSE = "togglepause";
195 public static final String CMDSTOP = "stop";
197 public static final String CMDPAUSE = "pause";
199 public static final String CMDPLAY = "play";
201 public static final String CMDPREVIOUS = "previous";
203 public static final String CMDNEXT = "next";
205 public static final String CMDNOTIF = "buttonId";
207 private static final int IDCOLIDX = 0;
210 * Moves a list to the front of the queue
212 public static final int NOW = 1;
215 * Moves a list to the next position in the queue
217 public static final int NEXT = 2;
220 * Moves a list to the last position in the queue
222 public static final int LAST = 3;
225 * Shuffles no songs, turns shuffling off
227 public static final int SHUFFLE_NONE = 0;
232 public static final int SHUFFLE_NORMAL = 1;
237 public static final int SHUFFLE_AUTO = 2;
242 public static final int REPEAT_NONE = 0;
245 * Repeats the current track in a list
247 public static final int REPEAT_CURRENT = 1;
250 * Repeats all the tracks in a list
252 public static final int REPEAT_ALL = 2;
255 * Indicates when the track ends
257 private static final int TRACK_ENDED = 1;
260 * Indicates that the current track was changed the next track
262 private static final int TRACK_WENT_TO_NEXT = 2;
265 * Indicates when the release the wake lock
267 private static final int RELEASE_WAKELOCK = 3;
270 * Indicates the player died
272 private static final int SERVER_DIED = 4;
275 * Indicates some sort of focus change, maybe a phone call
277 private static final int FOCUSCHANGE = 5;
280 * Indicates to fade the volume down
282 private static final int FADEDOWN = 6;
285 * Indicates to fade the volume back up
287 private static final int FADEUP = 7;
290 * Idle time before stopping the foreground notfication (1 minute)
292 private static final int IDLE_DELAY = 60000;
295 * Song play time used as threshold for rewinding to the beginning of the
296 * track instead of skipping to the previous track when getting the PREVIOUS
299 private static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 3000;
302 * The max size allowed for the track history
303 * TODO: Comeback and rewrite/fix all the whole queue code bugs after demo
305 public static final int MAX_HISTORY_SIZE = 1000;
308 * The columns used to retrieve any info from the current track
310 private static final String[] PROJECTION = new String[] {
311 "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
312 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
313 MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
314 MediaStore.Audio.Media.ARTIST_ID
318 * The columns used to retrieve any info from the current album
320 private static final String[] ALBUM_PROJECTION = new String[] {
321 MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
322 MediaStore.Audio.Albums.LAST_YEAR
326 * Keeps a mapping of the track history
328 private static final LinkedList<Integer> mHistory = Lists.newLinkedList();
331 * Used to shuffle the tracks
333 private static final Shuffler mShuffler = new Shuffler();
336 * Used to save the queue as reverse hexadecimal numbers, which we can
337 * generate faster than normal decimal or hexadecimal numbers, which in
338 * turn allows us to save the playlist more often without worrying too
339 * much about performance
341 private static final char HEX_DIGITS[] = new char[] {
342 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
348 private final IBinder mBinder = new ServiceStub(this);
353 private final AppWidgetSmall mAppWidgetSmall = AppWidgetSmall.getInstance();
358 private final AppWidgetLarge mAppWidgetLarge = AppWidgetLarge.getInstance();
361 * 4x2 alternate widget
363 private final AppWidgetLargeAlternate mAppWidgetLargeAlternate = AppWidgetLargeAlternate
369 private MultiPlayer mPlayer;
372 * The path of the current file to play
374 private String mFileToPlay;
377 * Keeps the service running when the screen is off
379 private WakeLock mWakeLock;
382 * Alarm intent for removing the notification when nothing is playing
385 private AlarmManager mAlarmManager;
386 private PendingIntent mShutdownIntent;
387 private boolean mShutdownScheduled;
390 * The cursor used to retrieve info on the current track and run the
391 * necessary queries to play audio files
393 private Cursor mCursor;
396 * The cursor used to retrieve info on the album the current track is
399 private Cursor mAlbumCursor;
402 * Monitors the audio state
404 private AudioManager mAudioManager;
407 * Settings used to save and retrieve the queue and history
409 private SharedPreferences mPreferences;
412 * Used to know when the service is active
414 private boolean mServiceInUse = false;
417 * Used to know if something should be playing or not
419 private boolean mIsSupposedToBePlaying = false;
422 * Used to indicate if the queue can be saved
424 private boolean mQueueIsSaveable = true;
427 * Used to track what type of audio focus loss caused the playback to pause
429 private boolean mPausedByTransientLossOfFocus = false;
432 * Used to track whether any of Apollo's activities is in the foreground
434 private boolean mAnyActivityInForeground = false;
437 * Lock screen controls
439 private RemoteControlClient mRemoteControlClient;
441 private ComponentName mMediaButtonReceiverComponent;
443 // We use this to distinguish between different cards when saving/restoring
447 private int mPlayListLen = 0;
449 private int mPlayPos = -1;
451 private int mNextPlayPos = -1;
453 private int mOpenFailedCounter = 0;
455 private int mMediaMountedCount = 0;
457 private int mShuffleMode = SHUFFLE_NONE;
459 private int mRepeatMode = REPEAT_NONE;
461 private int mServiceStartId = -1;
463 private long[] mPlayList = null;
465 private long[] mAutoShuffleList = null;
467 private MusicPlayerHandler mPlayerHandler;
469 private BroadcastReceiver mUnmountReceiver = null;
474 private ImageFetcher mImageFetcher;
477 * Used to build the notification
479 private NotificationHelper mNotificationHelper;
482 * Recently listened database
484 private RecentStore mRecentsCache;
487 * The song play count database
489 private SongPlayCount mSongPlayCountCache;
495 public IBinder onBind(final Intent intent) {
496 if (D) Log.d(TAG, "Service bound, intent = " + intent);
498 mServiceInUse = true;
506 public boolean onUnbind(final Intent intent) {
507 if (D) Log.d(TAG, "Service unbound");
508 mServiceInUse = false;
511 if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
512 // Something is currently playing, or will be playing once
513 // an in-progress action requesting audio focus ends, so don't stop
517 // If there is a playlist but playback is paused, then wait a while
518 // before stopping the service, so that pause/resume isn't slow.
519 // Also delay stopping the service if we're transitioning between
521 } else if (mPlayListLen > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
522 scheduleDelayedShutdown();
525 stopSelf(mServiceStartId);
533 public void onRebind(final Intent intent) {
535 mServiceInUse = true;
542 public void onCreate() {
543 if (D) Log.d(TAG, "Creating service");
546 // Initialize the favorites and recents databases
547 mRecentsCache = RecentStore.getInstance(this);
549 // gets the song play count cache
550 mSongPlayCountCache = SongPlayCount.getInstance(this);
552 // Initialize the notification helper
553 mNotificationHelper = new NotificationHelper(this);
555 // Initialize the image fetcher
556 mImageFetcher = ImageFetcher.getInstance(this);
557 // Initialize the image cache
558 mImageFetcher.setImageCache(ImageCache.getInstance(this));
560 // Start up the thread running the service. Note that we create a
561 // separate thread because the service normally runs in the process's
562 // main thread, which we don't want to block. We also make it
563 // background priority so CPU-intensive work will not disrupt the UI.
564 final HandlerThread thread = new HandlerThread("MusicPlayerHandler",
565 android.os.Process.THREAD_PRIORITY_BACKGROUND);
568 // Initialize the handler
569 mPlayerHandler = new MusicPlayerHandler(this, thread.getLooper());
571 // Initialize the audio manager and register any headset controls for
573 mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
574 mMediaButtonReceiverComponent = new ComponentName(getPackageName(),
575 MediaButtonIntentReceiver.class.getName());
576 mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
578 // Use the remote control APIs to set the playback state
579 setUpRemoteControlClient();
581 // Initialize the preferences
582 mPreferences = getSharedPreferences("Service", 0);
583 mCardId = getCardId();
585 registerExternalStorageListener();
587 // Initialize the media player
588 mPlayer = new MultiPlayer(this);
589 mPlayer.setHandler(mPlayerHandler);
591 // Initialize the intent filter and each action
592 final IntentFilter filter = new IntentFilter();
593 filter.addAction(SERVICECMD);
594 filter.addAction(TOGGLEPAUSE_ACTION);
595 filter.addAction(PAUSE_ACTION);
596 filter.addAction(STOP_ACTION);
597 filter.addAction(NEXT_ACTION);
598 filter.addAction(PREVIOUS_ACTION);
599 filter.addAction(PREVIOUS_FORCE_ACTION);
600 filter.addAction(REPEAT_ACTION);
601 filter.addAction(SHUFFLE_ACTION);
602 // Attach the broadcast listener
603 registerReceiver(mIntentReceiver, filter);
605 // Initialize the wake lock
606 final PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
607 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
608 mWakeLock.setReferenceCounted(false);
610 // Initialize the delayed shutdown intent
611 final Intent shutdownIntent = new Intent(this, MusicPlaybackService.class);
612 shutdownIntent.setAction(SHUTDOWN);
614 mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
615 mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);
617 // Listen for the idle state
618 scheduleDelayedShutdown();
620 // Bring the queue back
622 notifyChange(QUEUE_CHANGED);
623 notifyChange(META_CHANGED);
627 * Initializes the remote control client
629 private void setUpRemoteControlClient() {
630 final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
631 mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);
632 mRemoteControlClient = new RemoteControlClient(
633 PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
634 PendingIntent.FLAG_UPDATE_CURRENT));
635 mAudioManager.registerRemoteControlClient(mRemoteControlClient);
637 // Flags for the media transport control that this client supports.
638 int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
639 | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
640 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
641 | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
642 | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
643 | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
645 if (ApolloUtils.hasJellyBeanMR2()) {
646 flags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
648 mRemoteControlClient.setOnGetPlaybackPositionListener(
649 new RemoteControlClient.OnGetPlaybackPositionListener() {
651 public long onGetPlaybackPosition() {
655 mRemoteControlClient.setPlaybackPositionUpdateListener(
656 new RemoteControlClient.OnPlaybackPositionUpdateListener() {
658 public void onPlaybackPositionUpdate(long newPositionMs) {
664 mRemoteControlClient.setTransportControlFlags(flags);
671 public void onDestroy() {
672 if (D) Log.d(TAG, "Destroying service");
674 // Remove any sound effects
675 final Intent audioEffectsIntent = new Intent(
676 AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
677 audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
678 audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
679 sendBroadcast(audioEffectsIntent);
681 // remove any pending alarms
682 mAlarmManager.cancel(mShutdownIntent);
684 // Release the player
688 // Remove the audio focus listener and lock screen controls
689 mAudioManager.abandonAudioFocus(mAudioFocusListener);
690 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
692 // Remove any callbacks from the handler
693 mPlayerHandler.removeCallbacksAndMessages(null);
698 // Unregister the mount listener
699 unregisterReceiver(mIntentReceiver);
700 if (mUnmountReceiver != null) {
701 unregisterReceiver(mUnmountReceiver);
702 mUnmountReceiver = null;
705 // Release the wake lock
713 public int onStartCommand(final Intent intent, final int flags, final int startId) {
714 if (D) Log.d(TAG, "Got new intent " + intent + ", startId = " + startId);
715 mServiceStartId = startId;
717 if (intent != null) {
718 final String action = intent.getAction();
720 if (intent.hasExtra(NOW_IN_FOREGROUND)) {
721 mAnyActivityInForeground = intent.getBooleanExtra(NOW_IN_FOREGROUND, false);
722 updateNotification();
725 if (SHUTDOWN.equals(action)) {
726 mShutdownScheduled = false;
727 releaseServiceUiAndStop();
728 return START_NOT_STICKY;
731 handleCommandIntent(intent);
734 // Make sure the service will shut down on its own if it was
735 // just started but not bound to and nothing is playing
736 scheduleDelayedShutdown();
738 if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {
739 MediaButtonIntentReceiver.completeWakefulIntent(intent);
745 private void releaseServiceUiAndStop() {
747 || mPausedByTransientLossOfFocus
748 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
752 if (D) Log.d(TAG, "Nothing is playing anymore, releasing notification");
753 mNotificationHelper.killNotification();
754 mAudioManager.abandonAudioFocus(mAudioFocusListener);
756 if (!mServiceInUse) {
758 stopSelf(mServiceStartId);
762 private void handleCommandIntent(Intent intent) {
763 final String action = intent.getAction();
764 final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
766 if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
768 if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
770 } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
771 || PREVIOUS_FORCE_ACTION.equals(action)) {
772 prev(PREVIOUS_FORCE_ACTION.equals(action));
773 } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
776 mPausedByTransientLossOfFocus = false;
780 } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
782 mPausedByTransientLossOfFocus = false;
783 } else if (CMDPLAY.equals(command)) {
785 } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
787 mPausedByTransientLossOfFocus = false;
789 releaseServiceUiAndStop();
790 } else if (REPEAT_ACTION.equals(action)) {
792 } else if (SHUFFLE_ACTION.equals(action)) {
798 * Updates the notification, considering the current play and activity state
800 private void updateNotification() {
801 if (!mAnyActivityInForeground && isPlaying()) {
802 mNotificationHelper.buildNotification(getAlbumName(), getArtistName(),
803 getTrackName(), getAlbumId(), getAlbumArt(), isPlaying());
804 } else if (mAnyActivityInForeground) {
805 mNotificationHelper.killNotification();
810 * @return A card ID used to save and restore playlists, i.e., the queue.
812 private int getCardId() {
813 final ContentResolver resolver = getContentResolver();
814 Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
817 if (cursor != null && cursor.moveToFirst()) {
818 mCardId = cursor.getInt(0);
826 * Called when we receive a ACTION_MEDIA_EJECT notification.
828 * @param storagePath The path to mount point for the removed media
830 public void closeExternalStorageFiles(final String storagePath) {
832 notifyChange(QUEUE_CHANGED);
833 notifyChange(META_CHANGED);
837 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
838 * intent will call closeExternalStorageFiles() if the external media is
839 * going to be ejected, so applications can clean up any files they have
842 public void registerExternalStorageListener() {
843 if (mUnmountReceiver == null) {
844 mUnmountReceiver = new BroadcastReceiver() {
850 public void onReceive(final Context context, final Intent intent) {
851 final String action = intent.getAction();
852 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
854 mQueueIsSaveable = false;
855 closeExternalStorageFiles(intent.getData().getPath());
856 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
857 mMediaMountedCount++;
858 mCardId = getCardId();
860 mQueueIsSaveable = true;
861 notifyChange(QUEUE_CHANGED);
862 notifyChange(META_CHANGED);
866 final IntentFilter filter = new IntentFilter();
867 filter.addAction(Intent.ACTION_MEDIA_EJECT);
868 filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
869 filter.addDataScheme("file");
870 registerReceiver(mUnmountReceiver, filter);
874 private void scheduleDelayedShutdown() {
875 if (D) Log.v(TAG, "Scheduling shutdown in " + IDLE_DELAY + " ms");
876 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
877 SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);
878 mShutdownScheduled = true;
881 private void cancelShutdown() {
882 if (D) Log.d(TAG, "Cancelling delayed shutdown, scheduled = " + mShutdownScheduled);
883 if (mShutdownScheduled) {
884 mAlarmManager.cancel(mShutdownIntent);
885 mShutdownScheduled = false;
892 * @param goToIdle True to go to the idle state, false otherwise
894 private void stop(final boolean goToIdle) {
895 if (D) Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
896 if (mPlayer.isInitialized()) {
902 scheduleDelayedShutdown();
903 mIsSupposedToBePlaying = false;
905 stopForeground(false);
910 * Removes the range of tracks specified from the play list. If a file
911 * within the range is the file currently being played, playback will move
912 * to the next file after the range.
914 * @param first The first file to be removed
915 * @param last The last file to be removed
916 * @return the number of tracks deleted
918 private int removeTracksInternal(int first, int last) {
919 synchronized (this) {
922 } else if (first < 0) {
924 } else if (last >= mPlayListLen) {
925 last = mPlayListLen - 1;
928 boolean gotonext = false;
929 if (first <= mPlayPos && mPlayPos <= last) {
932 } else if (mPlayPos > last) {
933 mPlayPos -= last - first + 1;
935 final int num = mPlayListLen - last - 1;
936 for (int i = 0; i < num; i++) {
937 mPlayList[first + i] = mPlayList[last + 1 + i];
939 mPlayListLen -= last - first + 1;
942 if (mPlayListLen == 0) {
947 if (mShuffleMode != SHUFFLE_NONE) {
948 mPlayPos = getNextPosition(true);
949 } else if (mPlayPos >= mPlayListLen) {
952 final boolean wasPlaying = isPlaying();
954 openCurrentAndNext();
959 notifyChange(META_CHANGED);
961 return last - first + 1;
966 * Adds a list to the playlist
968 * @param list The list to add
969 * @param position The position to place the tracks
971 private void addToPlayList(final long[] list, int position) {
972 final int addlen = list.length;
977 ensurePlayListCapacity(mPlayListLen + addlen);
978 if (position > mPlayListLen) {
979 position = mPlayListLen;
982 final int tailsize = mPlayListLen - position;
983 for (int i = tailsize; i > 0; i--) {
984 mPlayList[position + i] = mPlayList[position + i - addlen];
987 for (int i = 0; i < addlen; i++) {
988 mPlayList[position + i] = list[i];
990 mPlayListLen += addlen;
991 if (mPlayListLen == 0) {
993 notifyChange(META_CHANGED);
998 * @param trackId The track ID
1000 private void updateCursor(final long trackId) {
1001 updateCursor("_id=" + trackId, null);
1004 private void updateCursor(final String selection, final String[] selectionArgs) {
1005 synchronized (this) {
1007 mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1008 PROJECTION, selection, selectionArgs);
1010 updateAlbumCursor();
1013 private void updateCursor(final Uri uri) {
1014 synchronized (this) {
1016 mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);
1018 updateAlbumCursor();
1021 private void updateAlbumCursor() {
1022 long albumId = getAlbumId();
1024 mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
1025 ALBUM_PROJECTION, "_id=" + albumId, null);
1027 mAlbumCursor = null;
1031 private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,
1032 String selection, String[] selectionArgs) {
1033 Cursor c = getContentResolver().query(uri, projection,
1034 selection, selectionArgs, null, null);
1038 if (!c.moveToFirst()) {
1045 private void closeCursor() {
1046 if (mCursor != null) {
1050 if (mAlbumCursor != null) {
1051 mAlbumCursor.close();
1052 mAlbumCursor = null;
1057 * Called to open a new file as the current track and prepare the next for
1060 private void openCurrentAndNext() {
1061 openCurrentAndMaybeNext(true);
1065 * Called to open a new file as the current track and prepare the next for
1068 * @param openNext True to prepare the next track for playback, false
1071 private void openCurrentAndMaybeNext(final boolean openNext) {
1072 synchronized (this) {
1075 if (mPlayListLen == 0) {
1080 updateCursor(mPlayList[mPlayPos]);
1083 && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
1084 + mCursor.getLong(IDCOLIDX))) {
1087 // if we get here then opening the file failed. We can close the
1088 // cursor now, because
1089 // we're either going to create a new one next, or stop trying
1091 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1092 final int pos = getNextPosition(false);
1094 scheduleDelayedShutdown();
1095 if (mIsSupposedToBePlaying) {
1096 mIsSupposedToBePlaying = false;
1097 notifyChange(PLAYSTATE_CHANGED);
1104 updateCursor(mPlayList[mPlayPos]);
1106 mOpenFailedCounter = 0;
1107 Log.w(TAG, "Failed to open file for playback");
1108 scheduleDelayedShutdown();
1109 if (mIsSupposedToBePlaying) {
1110 mIsSupposedToBePlaying = false;
1111 notifyChange(PLAYSTATE_CHANGED);
1123 * @param force True to force the player onto the track next, false
1125 * @param saveToHistory True to save the mPlayPos to the history
1126 * @return The next position to play.
1128 private int getNextPosition(final boolean force) {
1129 // if we're not forced to go to the next track and we are only playing the current track
1130 if (!force && mRepeatMode == REPEAT_CURRENT) {
1135 } else if (mShuffleMode == SHUFFLE_NORMAL) {
1136 final int numTracks = mPlayListLen;
1138 // count the number of times a track has been played
1139 final int[] trackNumPlays = new int[numTracks];
1140 for (int i = 0; i < numTracks; i++) {
1142 trackNumPlays[i] = 0;
1145 // walk through the history and add up the number of times the track
1147 final int numHistory = mHistory.size();
1148 for (int i = 0; i < numHistory; i++) {
1149 final int idx = mHistory.get(i).intValue();
1150 if (idx >= 0 && idx < numTracks) {
1151 trackNumPlays[idx]++;
1155 // also add the currently playing track to the count
1156 if (mPlayPos >= 0 && mPlayPos < numTracks) {
1157 trackNumPlays[mPlayPos]++;
1160 // figure out the least # of times a track has a played as well as
1161 // how many tracks share that count
1162 int minNumPlays = Integer.MAX_VALUE;
1163 int numTracksWithMinNumPlays = 0;
1164 for (int i = 0; i < trackNumPlays.length; i++) {
1165 // if we found a new track that has less number of plays, reset the counters
1166 if (trackNumPlays[i] < minNumPlays) {
1167 minNumPlays = trackNumPlays[i];
1168 numTracksWithMinNumPlays = 1;
1169 } else if (trackNumPlays[i] == minNumPlays) {
1170 // increment this track shares the # of tracks
1171 numTracksWithMinNumPlays++;
1175 // if we've played each track at least once
1176 if (minNumPlays > 0) {
1177 // if we aren't repeating all and we're not forcing a track
1178 // return no more tracks
1179 if (mRepeatMode != REPEAT_ALL && !force) {
1184 // else pick a track from the least number of played tracks
1185 int skip = mShuffler.nextInt(numTracksWithMinNumPlays);
1186 for (int i = 0; i < trackNumPlays.length; i++) {
1187 if (trackNumPlays[i] == minNumPlays) {
1196 // Unexpected to land here
1197 if (D) Log.e(TAG, "Getting the next position resulted did not get a result when it should have");
1199 } else if (mShuffleMode == SHUFFLE_AUTO) {
1200 doAutoShuffleUpdate();
1201 return mPlayPos + 1;
1203 if (mPlayPos >= mPlayListLen - 1) {
1204 if (mRepeatMode == REPEAT_NONE && !force) {
1206 } else if (mRepeatMode == REPEAT_ALL || force) {
1211 return mPlayPos + 1;
1217 * Sets the track to be played
1219 private void setNextTrack() {
1220 setNextTrack(getNextPosition(false));
1224 * Sets the next track to be played
1225 * @param position the target position we want
1227 private void setNextTrack(int position) {
1228 mNextPlayPos = position;
1229 if (D) Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
1230 if (mNextPlayPos >= 0 && mPlayList != null) {
1231 final long id = mPlayList[mNextPlayPos];
1232 mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
1234 mPlayer.setNextDataSource(null);
1239 * Creates a shuffled playlist used for party mode
1241 private boolean makeAutoShuffleList() {
1242 Cursor cursor = null;
1244 cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1246 MediaStore.Audio.Media._ID
1247 }, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
1248 if (cursor == null || cursor.getCount() == 0) {
1251 final int len = cursor.getCount();
1252 final long[] list = new long[len];
1253 for (int i = 0; i < len; i++) {
1254 cursor.moveToNext();
1255 list[i] = cursor.getLong(0);
1257 mAutoShuffleList = list;
1259 } catch (final RuntimeException e) {
1261 if (cursor != null) {
1270 * Creates the party shuffle playlist
1272 private void doAutoShuffleUpdate() {
1273 boolean notify = false;
1274 if (mPlayPos > 10) {
1275 removeTracks(0, mPlayPos - 9);
1278 final int toAdd = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1279 for (int i = 0; i < toAdd; i++) {
1280 int lookback = mHistory.size();
1283 idx = mShuffler.nextInt(mAutoShuffleList.length);
1284 if (!wasRecentlyUsed(idx, lookback)) {
1290 if (mHistory.size() > MAX_HISTORY_SIZE) {
1293 ensurePlayListCapacity(mPlayListLen + 1);
1294 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
1298 notifyChange(QUEUE_CHANGED);
1303 private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
1304 if (lookbacksize == 0) {
1307 final int histsize = mHistory.size();
1308 if (histsize < lookbacksize) {
1309 lookbacksize = histsize;
1311 final int maxidx = histsize - 1;
1312 for (int i = 0; i < lookbacksize; i++) {
1313 final long entry = mHistory.get(maxidx - i);
1322 * Makes sure the playlist has enough space to hold all of the songs
1324 * @param size The size of the playlist
1326 private void ensurePlayListCapacity(final int size) {
1327 if (mPlayList == null || size > mPlayList.length) {
1328 // reallocate at 2x requested size so we don't
1329 // need to grow and copy the array for every
1331 final long[] newlist = new long[size * 2];
1332 final int len = mPlayList != null ? mPlayList.length : mPlayListLen;
1333 for (int i = 0; i < len; i++) {
1334 newlist[i] = mPlayList[i];
1336 mPlayList = newlist;
1338 // FIXME: shrink the array when the needed size is much smaller
1339 // than the allocated size
1343 * Notify the change-receivers that something has changed.
1345 private void notifyChange(final String what) {
1346 if (D) Log.d(TAG, "notifyChange: what = " + what);
1348 // Update the lockscreen controls
1349 updateRemoteControlClient(what);
1351 if (what.equals(POSITION_CHANGED)) {
1355 final Intent intent = new Intent(what);
1356 intent.putExtra("id", getAudioId());
1357 intent.putExtra("artist", getArtistName());
1358 intent.putExtra("album", getAlbumName());
1359 intent.putExtra("track", getTrackName());
1360 intent.putExtra("playing", isPlaying());
1361 sendStickyBroadcast(intent);
1363 final Intent musicIntent = new Intent(intent);
1364 musicIntent.setAction(what.replace(ELEVEN_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
1365 sendStickyBroadcast(musicIntent);
1367 if (what.equals(META_CHANGED)) {
1368 // Add the track to the recently played list.
1369 mRecentsCache.addSongId(getAudioId());
1371 mSongPlayCountCache.bumpSongCount(getAudioId());
1372 } else if (what.equals(QUEUE_CHANGED)) {
1381 if (what.equals(PLAYSTATE_CHANGED)) {
1382 mNotificationHelper.updatePlayState(isPlaying());
1385 // Update the app-widgets
1386 mAppWidgetSmall.notifyChange(this, what);
1387 mAppWidgetLarge.notifyChange(this, what);
1388 mAppWidgetLargeAlternate.notifyChange(this, what);
1392 * Updates the lockscreen controls.
1394 * @param what The broadcast
1396 private void updateRemoteControlClient(final String what) {
1397 int playState = mIsSupposedToBePlaying
1398 ? RemoteControlClient.PLAYSTATE_PLAYING
1399 : RemoteControlClient.PLAYSTATE_PAUSED;
1401 if (ApolloUtils.hasJellyBeanMR2()
1402 && (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED))) {
1403 mRemoteControlClient.setPlaybackState(playState, position(), 1.0f);
1404 } else if (what.equals(PLAYSTATE_CHANGED)) {
1405 mRemoteControlClient.setPlaybackState(playState);
1406 } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
1407 Bitmap albumArt = getAlbumArt();
1408 if (albumArt != null) {
1409 // RemoteControlClient wants to recycle the bitmaps thrown at it, so we need
1410 // to make sure not to hand out our cache copy
1411 Bitmap.Config config = albumArt.getConfig();
1412 if (config == null) {
1413 config = Bitmap.Config.ARGB_8888;
1415 albumArt = albumArt.copy(config, false);
1417 mRemoteControlClient
1419 .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName())
1420 .putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
1421 getAlbumArtistName())
1422 .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName())
1423 .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName())
1424 .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration())
1425 .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt)
1428 if (ApolloUtils.hasJellyBeanMR2()) {
1429 mRemoteControlClient.setPlaybackState(playState, position(), 1.0f);
1437 * @param full True if the queue is full
1439 private void saveQueue(final boolean full) {
1440 if (!mQueueIsSaveable) {
1444 final SharedPreferences.Editor editor = mPreferences.edit();
1446 final StringBuilder q = new StringBuilder();
1447 int len = mPlayListLen;
1448 for (int i = 0; i < len; i++) {
1449 long n = mPlayList[i];
1452 } else if (n == 0) {
1456 final int digit = (int)(n & 0xf);
1458 q.append(HEX_DIGITS[digit]);
1463 editor.putString("queue", q.toString());
1464 editor.putInt("cardid", mCardId);
1465 if (mShuffleMode != SHUFFLE_NONE) {
1466 len = mHistory.size();
1468 for (int i = 0; i < len; i++) {
1469 int n = mHistory.get(i);
1474 final int digit = n & 0xf;
1476 q.append(HEX_DIGITS[digit]);
1481 editor.putString("history", q.toString());
1484 editor.putInt("curpos", mPlayPos);
1485 if (mPlayer.isInitialized()) {
1486 editor.putLong("seekpos", mPlayer.position());
1488 editor.putInt("repeatmode", mRepeatMode);
1489 editor.putInt("shufflemode", mShuffleMode);
1494 * Reloads the queue as the user left it the last time they stopped using
1497 private void reloadQueue() {
1500 if (mPreferences.contains("cardid")) {
1501 id = mPreferences.getInt("cardid", ~mCardId);
1503 if (id == mCardId) {
1504 q = mPreferences.getString("queue", "");
1506 int qlen = q != null ? q.length() : 0;
1511 for (int i = 0; i < qlen; i++) {
1512 final char c = q.charAt(i);
1514 ensurePlayListCapacity(plen + 1);
1515 mPlayList[plen] = n;
1520 if (c >= '0' && c <= '9') {
1521 n += c - '0' << shift;
1522 } else if (c >= 'a' && c <= 'f') {
1523 n += 10 + c - 'a' << shift;
1531 mPlayListLen = plen;
1532 final int pos = mPreferences.getInt("curpos", 0);
1533 if (pos < 0 || pos >= mPlayListLen) {
1538 updateCursor(mPlayList[mPlayPos]);
1539 if (mCursor == null) {
1540 SystemClock.sleep(3000);
1541 updateCursor(mPlayList[mPlayPos]);
1543 synchronized (this) {
1545 mOpenFailedCounter = 20;
1546 openCurrentAndNext();
1548 if (!mPlayer.isInitialized()) {
1553 final long seekpos = mPreferences.getLong("seekpos", 0);
1554 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
1557 Log.d(TAG, "restored queue, currently at position "
1558 + position() + "/" + duration()
1559 + " (requested " + seekpos + ")");
1562 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
1563 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
1564 repmode = REPEAT_NONE;
1566 mRepeatMode = repmode;
1568 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
1569 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
1570 shufmode = SHUFFLE_NONE;
1572 if (shufmode != SHUFFLE_NONE) {
1573 q = mPreferences.getString("history", "");
1574 qlen = q != null ? q.length() : 0;
1580 for (int i = 0; i < qlen; i++) {
1581 final char c = q.charAt(i);
1583 if (n >= mPlayListLen) {
1591 if (c >= '0' && c <= '9') {
1592 n += c - '0' << shift;
1593 } else if (c >= 'a' && c <= 'f') {
1594 n += 10 + c - 'a' << shift;
1604 if (shufmode == SHUFFLE_AUTO) {
1605 if (!makeAutoShuffleList()) {
1606 shufmode = SHUFFLE_NONE;
1609 mShuffleMode = shufmode;
1614 * Opens a file and prepares it for playback
1616 * @param path The path of the file to open
1618 public boolean openFile(final String path) {
1619 if (D) Log.d(TAG, "openFile: path = " + path);
1620 synchronized (this) {
1625 // If mCursor is null, try to associate path with a database cursor
1626 if (mCursor == null) {
1627 Uri uri = Uri.parse(path);
1630 id = Long.valueOf(uri.getLastPathSegment());
1631 } catch (NumberFormatException ex) {
1635 if (id != -1 && path.startsWith(MediaStore.Audio.Media.
1636 EXTERNAL_CONTENT_URI.toString())) {
1639 } else if (id != -1 && path.startsWith(MediaStore.Files.getContentUri(
1640 "external").toString())) {
1644 String where = MediaStore.Audio.Media.DATA + "=?";
1645 String[] selectionArgs = new String[] {path};
1646 updateCursor(where, selectionArgs);
1649 if (mCursor != null) {
1650 ensurePlayListCapacity(1);
1652 mPlayList[0] = mCursor.getLong(IDCOLIDX);
1656 } catch (final UnsupportedOperationException ex) {
1660 mPlayer.setDataSource(mFileToPlay);
1661 if (mPlayer.isInitialized()) {
1662 mOpenFailedCounter = 0;
1671 * Returns the audio session ID
1673 * @return The current media player audio session ID
1675 public int getAudioSessionId() {
1676 synchronized (this) {
1677 return mPlayer.getAudioSessionId();
1682 * Indicates if the media storeage device has been mounted or not
1684 * @return 1 if Intent.ACTION_MEDIA_MOUNTED is called, 0 otherwise
1686 public int getMediaMountedCount() {
1687 return mMediaMountedCount;
1691 * Returns the shuffle mode
1693 * @return The current shuffle mode (all, party, none)
1695 public int getShuffleMode() {
1696 return mShuffleMode;
1700 * Returns the repeat mode
1702 * @return The current repeat mode (all, one, none)
1704 public int getRepeatMode() {
1709 * Removes all instances of the track with the given ID from the playlist.
1711 * @param id The id to be removed
1712 * @return how many instances of the track were removed
1714 public int removeTrack(final long id) {
1716 synchronized (this) {
1717 for (int i = 0; i < mPlayListLen; i++) {
1718 if (mPlayList[i] == id) {
1719 numremoved += removeTracksInternal(i, i);
1724 if (numremoved > 0) {
1725 notifyChange(QUEUE_CHANGED);
1731 * Removes the range of tracks specified from the play list. If a file
1732 * within the range is the file currently being played, playback will move
1733 * to the next file after the range.
1735 * @param first The first file to be removed
1736 * @param last The last file to be removed
1737 * @return the number of tracks deleted
1739 public int removeTracks(final int first, final int last) {
1740 final int numremoved = removeTracksInternal(first, last);
1741 if (numremoved > 0) {
1742 notifyChange(QUEUE_CHANGED);
1748 * Returns the position in the queue
1750 * @return the current position in the queue
1752 public int getQueuePosition() {
1753 synchronized (this) {
1759 * @return the size of the queue history cache
1761 public int getQueueHistorySize() {
1762 synchronized (this) {
1763 return mHistory.size();
1768 * @return the queue of history positions
1770 public int[] getQueueHistoryList() {
1771 synchronized (this) {
1772 int[] history = new int[mHistory.size()];
1773 for (int i = 0; i < mHistory.size(); i++) {
1774 history[i] = mHistory.get(i);
1782 * Returns the path to current song
1784 * @return The path to the current song
1786 public String getPath() {
1787 synchronized (this) {
1788 if (mCursor == null) {
1791 return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
1796 * Returns the album name
1798 * @return The current song album Name
1800 public String getAlbumName() {
1801 synchronized (this) {
1802 if (mCursor == null) {
1805 return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
1810 * Returns the song name
1812 * @return The current song name
1814 public String getTrackName() {
1815 synchronized (this) {
1816 if (mCursor == null) {
1819 return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
1824 * Returns the artist name
1826 * @return The current song artist name
1828 public String getArtistName() {
1829 synchronized (this) {
1830 if (mCursor == null) {
1833 return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));
1838 * Returns the artist name
1840 * @return The current song artist name
1842 public String getAlbumArtistName() {
1843 synchronized (this) {
1844 if (mAlbumCursor == null) {
1847 return mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(AlbumColumns.ARTIST));
1852 * Returns the album ID
1854 * @return The current song album ID
1856 public long getAlbumId() {
1857 synchronized (this) {
1858 if (mCursor == null) {
1861 return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
1866 * Returns the artist ID
1868 * @return The current song artist ID
1870 public long getArtistId() {
1871 synchronized (this) {
1872 if (mCursor == null) {
1875 return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));
1880 * Returns the current audio ID
1882 * @return The current track ID
1884 public long getAudioId() {
1885 synchronized (this) {
1886 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1887 return mPlayList[mPlayPos];
1894 * Returns the next audio ID
1896 * @return The next track ID
1898 public long getNextAudioId() {
1899 synchronized (this) {
1900 if (mNextPlayPos >= 0 && mPlayer.isInitialized()) {
1901 return mPlayList[mNextPlayPos];
1908 * Returns the previous audio ID
1910 * @return The previous track ID
1912 public long getPreviousAudioId() {
1913 synchronized (this) {
1914 if (mPlayer.isInitialized()) {
1915 int pos = getPreviousPlayPosition(false);
1917 return mPlayList[pos];
1925 * Seeks the current track to a specific time
1927 * @param position The time to seek to
1928 * @return The time to play the track at
1930 public long seek(long position) {
1931 if (mPlayer.isInitialized()) {
1934 } else if (position > mPlayer.duration()) {
1935 position = mPlayer.duration();
1937 long result = mPlayer.seek(position);
1938 notifyChange(POSITION_CHANGED);
1945 * Returns the current position in time of the currenttrack
1947 * @return The current playback position in miliseconds
1949 public long position() {
1950 if (mPlayer.isInitialized()) {
1951 return mPlayer.position();
1957 * Returns the full duration of the current track
1959 * @return The duration of the current track in miliseconds
1961 public long duration() {
1962 if (mPlayer.isInitialized()) {
1963 return mPlayer.duration();
1971 * @return The queue as a long[]
1973 public long[] getQueue() {
1974 synchronized (this) {
1975 final int len = mPlayListLen;
1976 final long[] list = new long[len];
1977 for (int i = 0; i < len; i++) {
1978 list[i] = mPlayList[i];
1985 * @return True if music is playing, false otherwise
1987 public boolean isPlaying() {
1988 return mIsSupposedToBePlaying;
1992 * Opens a list for playback
1994 * @param list The list of tracks to open
1995 * @param position The position to start playback at
1997 public void open(final long[] list, final int position) {
1998 synchronized (this) {
1999 if (mShuffleMode == SHUFFLE_AUTO) {
2000 mShuffleMode = SHUFFLE_NORMAL;
2002 final long oldId = getAudioId();
2003 final int listlength = list.length;
2004 boolean newlist = true;
2005 if (mPlayListLen == listlength) {
2007 for (int i = 0; i < listlength; i++) {
2008 if (list[i] != mPlayList[i]) {
2015 addToPlayList(list, -1);
2016 notifyChange(QUEUE_CHANGED);
2018 if (position >= 0) {
2019 mPlayPos = position;
2021 mPlayPos = mShuffler.nextInt(mPlayListLen);
2024 openCurrentAndNext();
2025 if (oldId != getAudioId()) {
2026 notifyChange(META_CHANGED);
2034 public void stop() {
2039 * Resumes or starts playback.
2041 public void play() {
2046 * Resumes or starts playback.
2047 * @param createNewNextTrack True if you want to figure out the next track, false
2048 * if you want to re-use the existing next track (used for going back)
2050 public void play(boolean createNewNextTrack) {
2051 int status = mAudioManager.requestAudioFocus(mAudioFocusListener,
2052 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
2054 if (D) Log.d(TAG, "Starting playback: audio focus request status = " + status);
2056 if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
2060 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
2061 MediaButtonIntentReceiver.class.getName()));
2063 if (createNewNextTrack) {
2066 setNextTrack(mNextPlayPos);
2069 if (mPlayer.isInitialized()) {
2070 final long duration = mPlayer.duration();
2071 if (mRepeatMode != REPEAT_CURRENT && duration > 2000
2072 && mPlayer.position() >= duration - 2000) {
2077 mPlayerHandler.removeMessages(FADEDOWN);
2078 mPlayerHandler.sendEmptyMessage(FADEUP);
2080 if (!mIsSupposedToBePlaying) {
2081 mIsSupposedToBePlaying = true;
2082 notifyChange(PLAYSTATE_CHANGED);
2086 updateNotification();
2087 } else if (mPlayListLen <= 0) {
2088 setShuffleMode(SHUFFLE_AUTO);
2093 * Temporarily pauses playback.
2095 public void pause() {
2096 if (D) Log.d(TAG, "Pausing playback");
2097 synchronized (this) {
2098 mPlayerHandler.removeMessages(FADEUP);
2099 if (mIsSupposedToBePlaying) {
2101 scheduleDelayedShutdown();
2102 mIsSupposedToBePlaying = false;
2103 notifyChange(PLAYSTATE_CHANGED);
2109 * Changes from the current track to the next track
2111 public void gotoNext(final boolean force) {
2112 if (D) Log.d(TAG, "Going to next track");
2113 synchronized (this) {
2114 if (mPlayListLen <= 0) {
2115 if (D) Log.d(TAG, "No play queue");
2116 scheduleDelayedShutdown();
2119 int pos = mNextPlayPos;
2121 pos = getNextPosition(force);
2125 scheduleDelayedShutdown();
2126 if (mIsSupposedToBePlaying) {
2127 mIsSupposedToBePlaying = false;
2128 notifyChange(PLAYSTATE_CHANGED);
2134 setAndRecordPlayPos(pos);
2135 openCurrentAndNext();
2137 notifyChange(META_CHANGED);
2141 public void setAndRecordPlayPos(int nextPos) {
2142 synchronized (this) {
2143 // save to the history
2144 if (mShuffleMode != SHUFFLE_NONE) {
2145 mHistory.add(mPlayPos);
2146 if (mHistory.size() > MAX_HISTORY_SIZE) {
2156 * Changes from the current track to the previous played track
2158 public void prev(boolean forcePrevious) {
2159 synchronized (this) {
2160 // if we aren't repeating 1, and we are either early in the song
2161 // or we want to force go back, then go to the prevous track
2162 boolean goPrevious = getRepeatMode() != REPEAT_CURRENT &&
2163 (position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);
2166 if (D) Log.d(TAG, "Going to previous track");
2167 int pos = getPreviousPlayPosition(true);
2168 // if we have no more previous tracks, quit
2172 mNextPlayPos = mPlayPos;
2177 notifyChange(META_CHANGED);
2179 if (D) Log.d(TAG, "Going to beginning of track");
2186 public int getPreviousPlayPosition(boolean removeFromHistory) {
2187 synchronized (this) {
2188 if (mShuffleMode == SHUFFLE_NORMAL) {
2189 // Go to previously-played track and remove it from the history
2190 final int histsize = mHistory.size();
2191 if (histsize == 0) {
2194 final Integer pos = mHistory.get(histsize - 1);
2195 if (removeFromHistory) {
2196 mHistory.remove(histsize - 1);
2198 return pos.intValue();
2201 return mPlayPos - 1;
2203 return mPlayListLen - 1;
2210 * We don't want to open the current and next track when the user is using
2211 * the {@code #prev()} method because they won't be able to travel back to
2212 * the previously listened track if they're shuffling.
2214 private void openCurrent() {
2215 openCurrentAndMaybeNext(false);
2219 * Moves an item in the queue from one position to another
2221 * @param from The position the item is currently at
2222 * @param to The position the item is being moved to
2224 public void moveQueueItem(int index1, int index2) {
2225 synchronized (this) {
2226 if (index1 >= mPlayListLen) {
2227 index1 = mPlayListLen - 1;
2229 if (index2 >= mPlayListLen) {
2230 index2 = mPlayListLen - 1;
2232 if (index1 < index2) {
2233 final long tmp = mPlayList[index1];
2234 for (int i = index1; i < index2; i++) {
2235 mPlayList[i] = mPlayList[i + 1];
2237 mPlayList[index2] = tmp;
2238 if (mPlayPos == index1) {
2240 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
2243 } else if (index2 < index1) {
2244 final long tmp = mPlayList[index1];
2245 for (int i = index1; i > index2; i--) {
2246 mPlayList[i] = mPlayList[i - 1];
2248 mPlayList[index2] = tmp;
2249 if (mPlayPos == index1) {
2251 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
2255 notifyChange(QUEUE_CHANGED);
2260 * Sets the repeat mode
2262 * @param repeatmode The repeat mode to use
2264 public void setRepeatMode(final int repeatmode) {
2265 synchronized (this) {
2266 mRepeatMode = repeatmode;
2269 notifyChange(REPEATMODE_CHANGED);
2274 * Sets the shuffle mode
2276 * @param shufflemode The shuffle mode to use
2278 public void setShuffleMode(final int shufflemode) {
2279 synchronized (this) {
2280 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
2284 mShuffleMode = shufflemode;
2285 if (mShuffleMode == SHUFFLE_AUTO) {
2286 if (makeAutoShuffleList()) {
2288 doAutoShuffleUpdate();
2290 openCurrentAndNext();
2292 notifyChange(META_CHANGED);
2295 mShuffleMode = SHUFFLE_NONE;
2301 notifyChange(SHUFFLEMODE_CHANGED);
2306 * Sets the position of a track in the queue
2308 * @param index The position to place the track
2310 public void setQueuePosition(final int index) {
2311 synchronized (this) {
2314 openCurrentAndNext();
2316 notifyChange(META_CHANGED);
2317 if (mShuffleMode == SHUFFLE_AUTO) {
2318 doAutoShuffleUpdate();
2324 * Queues a new list for playback
2326 * @param list The list to queue
2327 * @param action The action to take
2329 public void enqueue(final long[] list, final int action) {
2330 synchronized (this) {
2331 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
2332 addToPlayList(list, mPlayPos + 1);
2333 notifyChange(QUEUE_CHANGED);
2335 addToPlayList(list, Integer.MAX_VALUE);
2336 notifyChange(QUEUE_CHANGED);
2337 if (action == NOW) {
2338 mPlayPos = mPlayListLen - list.length;
2339 openCurrentAndNext();
2341 notifyChange(META_CHANGED);
2347 openCurrentAndNext();
2349 notifyChange(META_CHANGED);
2355 * Cycles through the different repeat modes
2357 private void cycleRepeat() {
2358 if (mRepeatMode == REPEAT_NONE) {
2359 setRepeatMode(REPEAT_ALL);
2360 } else if (mRepeatMode == REPEAT_ALL) {
2361 setRepeatMode(REPEAT_CURRENT);
2362 if (mShuffleMode != SHUFFLE_NONE) {
2363 setShuffleMode(SHUFFLE_NONE);
2366 setRepeatMode(REPEAT_NONE);
2371 * Cycles through the different shuffle modes
2373 private void cycleShuffle() {
2374 if (mShuffleMode == SHUFFLE_NONE) {
2375 setShuffleMode(SHUFFLE_NORMAL);
2376 if (mRepeatMode == REPEAT_CURRENT) {
2377 setRepeatMode(REPEAT_ALL);
2379 } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
2380 setShuffleMode(SHUFFLE_NONE);
2385 * @return The album art for the current album.
2387 public Bitmap getAlbumArt() {
2388 // Return the cached artwork
2389 final Bitmap bitmap = mImageFetcher.getArtwork(getAlbumName(),
2390 getAlbumId(), getArtistName());
2395 * Called when one of the lists should refresh or requery.
2397 public void refresh() {
2398 notifyChange(REFRESH);
2402 * Called when one of the playlists have changed (renamed, added/removed tracks)
2404 public void playlistChanged() {
2405 notifyChange(PLAYLIST_CHANGED);
2408 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2413 public void onReceive(final Context context, final Intent intent) {
2414 final String command = intent.getStringExtra(CMDNAME);
2416 if (AppWidgetSmall.CMDAPPWIDGETUPDATE.equals(command)) {
2417 final int[] small = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2418 mAppWidgetSmall.performUpdate(MusicPlaybackService.this, small);
2419 } else if (AppWidgetLarge.CMDAPPWIDGETUPDATE.equals(command)) {
2420 final int[] large = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2421 mAppWidgetLarge.performUpdate(MusicPlaybackService.this, large);
2422 } else if (AppWidgetLargeAlternate.CMDAPPWIDGETUPDATE.equals(command)) {
2423 final int[] largeAlt = intent
2424 .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
2425 mAppWidgetLargeAlternate.performUpdate(MusicPlaybackService.this, largeAlt);
2427 handleCommandIntent(intent);
2432 private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
2437 public void onAudioFocusChange(final int focusChange) {
2438 mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
2442 private static final class MusicPlayerHandler extends Handler {
2443 private final WeakReference<MusicPlaybackService> mService;
2444 private float mCurrentVolume = 1.0f;
2447 * Constructor of <code>MusicPlayerHandler</code>
2449 * @param service The service to use.
2450 * @param looper The thread to run on.
2452 public MusicPlayerHandler(final MusicPlaybackService service, final Looper looper) {
2454 mService = new WeakReference<MusicPlaybackService>(service);
2461 public void handleMessage(final Message msg) {
2462 final MusicPlaybackService service = mService.get();
2463 if (service == null) {
2469 mCurrentVolume -= .05f;
2470 if (mCurrentVolume > .2f) {
2471 sendEmptyMessageDelayed(FADEDOWN, 10);
2473 mCurrentVolume = .2f;
2475 service.mPlayer.setVolume(mCurrentVolume);
2478 mCurrentVolume += .01f;
2479 if (mCurrentVolume < 1.0f) {
2480 sendEmptyMessageDelayed(FADEUP, 10);
2482 mCurrentVolume = 1.0f;
2484 service.mPlayer.setVolume(mCurrentVolume);
2487 if (service.isPlaying()) {
2488 service.gotoNext(true);
2490 service.openCurrentAndNext();
2493 case TRACK_WENT_TO_NEXT:
2494 service.setAndRecordPlayPos(service.mNextPlayPos);
2495 service.setNextTrack();
2496 if (service.mCursor != null) {
2497 service.mCursor.close();
2499 service.updateCursor(service.mPlayList[service.mPlayPos]);
2500 service.notifyChange(META_CHANGED);
2501 service.updateNotification();
2504 if (service.mRepeatMode == REPEAT_CURRENT) {
2508 service.gotoNext(false);
2511 case RELEASE_WAKELOCK:
2512 service.mWakeLock.release();
2515 if (D) Log.d(TAG, "Received audio focus change event " + msg.arg1);
2517 case AudioManager.AUDIOFOCUS_LOSS:
2518 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
2519 if (service.isPlaying()) {
2520 service.mPausedByTransientLossOfFocus =
2521 msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
2525 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
2526 removeMessages(FADEUP);
2527 sendEmptyMessage(FADEDOWN);
2529 case AudioManager.AUDIOFOCUS_GAIN:
2530 if (!service.isPlaying()
2531 && service.mPausedByTransientLossOfFocus) {
2532 service.mPausedByTransientLossOfFocus = false;
2533 mCurrentVolume = 0f;
2534 service.mPlayer.setVolume(mCurrentVolume);
2537 removeMessages(FADEDOWN);
2538 sendEmptyMessage(FADEUP);
2550 private static final class Shuffler {
2552 private final LinkedList<Integer> mHistoryOfNumbers = new LinkedList<Integer>();
2554 private final TreeSet<Integer> mPreviousNumbers = new TreeSet<Integer>();
2556 private final Random mRandom = new Random();
2558 private int mPrevious;
2561 * Constructor of <code>Shuffler</code>
2568 * @param interval The length the queue
2569 * @return The position of the next track to play
2571 public int nextInt(final int interval) {
2574 next = mRandom.nextInt(interval);
2575 } while (next == mPrevious && interval > 1
2576 && !mPreviousNumbers.contains(Integer.valueOf(next)));
2578 mHistoryOfNumbers.add(mPrevious);
2579 mPreviousNumbers.add(mPrevious);
2585 * Removes old tracks and cleans up the history preparing for new tracks
2586 * to be added to the mapping
2588 private void cleanUpHistory() {
2589 if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {
2590 for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {
2591 mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());
2597 private static final class MultiPlayer implements MediaPlayer.OnErrorListener,
2598 MediaPlayer.OnCompletionListener {
2600 private final WeakReference<MusicPlaybackService> mService;
2602 private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
2604 private MediaPlayer mNextMediaPlayer;
2606 private Handler mHandler;
2608 private boolean mIsInitialized = false;
2611 * Constructor of <code>MultiPlayer</code>
2613 public MultiPlayer(final MusicPlaybackService service) {
2614 mService = new WeakReference<MusicPlaybackService>(service);
2615 mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2619 * @param path The path of the file, or the http/rtsp URL of the stream
2622 public void setDataSource(final String path) {
2623 mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
2624 if (mIsInitialized) {
2625 setNextDataSource(null);
2630 * @param player The {@link MediaPlayer} to use
2631 * @param path The path of the file, or the http/rtsp URL of the stream
2633 * @return True if the <code>player</code> has been prepared and is
2634 * ready to play, false otherwise
2636 private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
2639 player.setOnPreparedListener(null);
2640 if (path.startsWith("content://")) {
2641 player.setDataSource(mService.get(), Uri.parse(path));
2643 player.setDataSource(path);
2645 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
2647 } catch (final IOException todo) {
2648 // TODO: notify the user why the file couldn't be opened
2650 } catch (final IllegalArgumentException todo) {
2651 // TODO: notify the user why the file couldn't be opened
2654 player.setOnCompletionListener(this);
2655 player.setOnErrorListener(this);
2656 final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
2657 intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
2658 intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, mService.get().getPackageName());
2659 mService.get().sendBroadcast(intent);
2664 * Set the MediaPlayer to start when this MediaPlayer finishes playback.
2666 * @param path The path of the file, or the http/rtsp URL of the stream
2669 public void setNextDataSource(final String path) {
2671 mCurrentMediaPlayer.setNextMediaPlayer(null);
2672 } catch (IllegalArgumentException e) {
2673 Log.i(TAG, "Next media player is current one, continuing");
2674 } catch (IllegalStateException e) {
2675 Log.e(TAG, "Media player not initialized!");
2678 if (mNextMediaPlayer != null) {
2679 mNextMediaPlayer.release();
2680 mNextMediaPlayer = null;
2685 mNextMediaPlayer = new MediaPlayer();
2686 mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2687 mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
2688 if (setDataSourceImpl(mNextMediaPlayer, path)) {
2689 mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
2691 if (mNextMediaPlayer != null) {
2692 mNextMediaPlayer.release();
2693 mNextMediaPlayer = null;
2701 * @param handler The handler to use
2703 public void setHandler(final Handler handler) {
2708 * @return True if the player is ready to go, false otherwise
2710 public boolean isInitialized() {
2711 return mIsInitialized;
2715 * Starts or resumes playback.
2717 public void start() {
2718 mCurrentMediaPlayer.start();
2722 * Resets the MediaPlayer to its uninitialized state.
2724 public void stop() {
2725 mCurrentMediaPlayer.reset();
2726 mIsInitialized = false;
2730 * Releases resources associated with this MediaPlayer object.
2732 public void release() {
2734 mCurrentMediaPlayer.release();
2738 * Pauses playback. Call start() to resume.
2740 public void pause() {
2741 mCurrentMediaPlayer.pause();
2745 * Gets the duration of the file.
2747 * @return The duration in milliseconds
2749 public long duration() {
2750 return mCurrentMediaPlayer.getDuration();
2754 * Gets the current playback position.
2756 * @return The current position in milliseconds
2758 public long position() {
2759 return mCurrentMediaPlayer.getCurrentPosition();
2763 * Gets the current playback position.
2765 * @param whereto The offset in milliseconds from the start to seek to
2766 * @return The offset in milliseconds from the start to seek to
2768 public long seek(final long whereto) {
2769 mCurrentMediaPlayer.seekTo((int)whereto);
2774 * Sets the volume on this player.
2776 * @param vol Left and right volume scalar
2778 public void setVolume(final float vol) {
2779 mCurrentMediaPlayer.setVolume(vol, vol);
2783 * Sets the audio session ID.
2785 * @param sessionId The audio session ID
2787 public void setAudioSessionId(final int sessionId) {
2788 mCurrentMediaPlayer.setAudioSessionId(sessionId);
2792 * Returns the audio session ID.
2794 * @return The current audio session ID.
2796 public int getAudioSessionId() {
2797 return mCurrentMediaPlayer.getAudioSessionId();
2804 public boolean onError(final MediaPlayer mp, final int what, final int extra) {
2806 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
2807 mIsInitialized = false;
2808 mCurrentMediaPlayer.release();
2809 mCurrentMediaPlayer = new MediaPlayer();
2810 mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
2811 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
2823 public void onCompletion(final MediaPlayer mp) {
2824 if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
2825 mCurrentMediaPlayer.release();
2826 mCurrentMediaPlayer = mNextMediaPlayer;
2827 mNextMediaPlayer = null;
2828 mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
2830 mService.get().mWakeLock.acquire(30000);
2831 mHandler.sendEmptyMessage(TRACK_ENDED);
2832 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
2837 private static final class ServiceStub extends IElevenService.Stub {
2839 private final WeakReference<MusicPlaybackService> mService;
2841 private ServiceStub(final MusicPlaybackService service) {
2842 mService = new WeakReference<MusicPlaybackService>(service);
2849 public void openFile(final String path) throws RemoteException {
2850 mService.get().openFile(path);
2857 public void open(final long[] list, final int position) throws RemoteException {
2858 mService.get().open(list, position);
2865 public void stop() throws RemoteException {
2866 mService.get().stop();
2873 public void pause() throws RemoteException {
2874 mService.get().pause();
2881 public void play() throws RemoteException {
2882 mService.get().play();
2889 public void prev(boolean forcePrevious) throws RemoteException {
2890 mService.get().prev(forcePrevious);
2897 public void next() throws RemoteException {
2898 mService.get().gotoNext(true);
2905 public void enqueue(final long[] list, final int action) throws RemoteException {
2906 mService.get().enqueue(list, action);
2913 public void setQueuePosition(final int index) throws RemoteException {
2914 mService.get().setQueuePosition(index);
2921 public void setShuffleMode(final int shufflemode) throws RemoteException {
2922 mService.get().setShuffleMode(shufflemode);
2929 public void setRepeatMode(final int repeatmode) throws RemoteException {
2930 mService.get().setRepeatMode(repeatmode);
2937 public void moveQueueItem(final int from, final int to) throws RemoteException {
2938 mService.get().moveQueueItem(from, to);
2945 public void refresh() throws RemoteException {
2946 mService.get().refresh();
2953 public void playlistChanged() throws RemoteException {
2954 mService.get().playlistChanged();
2961 public boolean isPlaying() throws RemoteException {
2962 return mService.get().isPlaying();
2969 public long[] getQueue() throws RemoteException {
2970 return mService.get().getQueue();
2977 public int getQueueHistorySize() throws RemoteException {
2978 return mService.get().getQueueHistorySize();
2985 public int[] getQueueHistoryList() throws RemoteException {
2986 return mService.get().getQueueHistoryList();
2993 public long duration() throws RemoteException {
2994 return mService.get().duration();
3001 public long position() throws RemoteException {
3002 return mService.get().position();
3009 public long seek(final long position) throws RemoteException {
3010 return mService.get().seek(position);
3017 public long getAudioId() throws RemoteException {
3018 return mService.get().getAudioId();
3025 public long getNextAudioId() throws RemoteException {
3026 return mService.get().getNextAudioId();
3033 public long getPreviousAudioId() throws RemoteException {
3034 return mService.get().getPreviousAudioId();
3041 public long getArtistId() throws RemoteException {
3042 return mService.get().getArtistId();
3049 public long getAlbumId() throws RemoteException {
3050 return mService.get().getAlbumId();
3057 public String getArtistName() throws RemoteException {
3058 return mService.get().getArtistName();
3065 public String getTrackName() throws RemoteException {
3066 return mService.get().getTrackName();
3073 public String getAlbumName() throws RemoteException {
3074 return mService.get().getAlbumName();
3081 public String getPath() throws RemoteException {
3082 return mService.get().getPath();
3089 public int getQueuePosition() throws RemoteException {
3090 return mService.get().getQueuePosition();
3097 public int getShuffleMode() throws RemoteException {
3098 return mService.get().getShuffleMode();
3105 public int getRepeatMode() throws RemoteException {
3106 return mService.get().getRepeatMode();
3113 public int removeTracks(final int first, final int last) throws RemoteException {
3114 return mService.get().removeTracks(first, last);
3121 public int removeTrack(final long id) throws RemoteException {
3122 return mService.get().removeTrack(id);
3129 public int getMediaMountedCount() throws RemoteException {
3130 return mService.get().getMediaMountedCount();
3137 public int getAudioSessionId() throws RemoteException {
3138 return mService.get().getAudioSessionId();