blob: 873d7ca7af4fc05c1222c3dd92cf12f8ca9e1eb2 [file] [log] [blame]
The Android Open Source Project792a2202009-03-03 19:32:30 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.music;
18
19import android.app.Notification;
The Android Open Source Project792a2202009-03-03 19:32:30 -080020import android.app.PendingIntent;
21import android.app.Service;
The Android Open Source Project490384b2009-03-11 12:11:59 -070022import android.appwidget.AppWidgetManager;
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -070023import android.content.ComponentName;
The Android Open Source Project792a2202009-03-03 19:32:30 -080024import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.BroadcastReceiver;
31import android.content.SharedPreferences;
32import android.content.SharedPreferences.Editor;
33import android.database.Cursor;
34import android.database.sqlite.SQLiteException;
Marco Nelissen8717d342011-09-08 09:32:51 -070035import android.graphics.Bitmap;
Marco Nelissenf2ef3b52010-09-21 15:47:27 -070036import android.media.audiofx.AudioEffect;
The Android Open Source Project792a2202009-03-03 19:32:30 -080037import android.media.AudioManager;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -080038import android.media.AudioManager.OnAudioFocusChangeListener;
Marco Nelissen8717d342011-09-08 09:32:51 -070039import android.media.MediaMetadataRetriever;
The Android Open Source Project792a2202009-03-03 19:32:30 -080040import android.media.MediaPlayer;
Marco Nelissen8717d342011-09-08 09:32:51 -070041import android.media.RemoteControlClient;
42import android.media.RemoteControlClient.MetadataEditor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080043import android.net.Uri;
The Android Open Source Project792a2202009-03-03 19:32:30 -080044import android.os.Handler;
45import android.os.IBinder;
46import android.os.Message;
47import android.os.PowerManager;
48import android.os.SystemClock;
49import android.os.PowerManager.WakeLock;
50import android.provider.MediaStore;
51import android.util.Log;
52import android.widget.RemoteViews;
53import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080054
Marco Nelissen39888902010-03-02 10:27:05 -080055import java.io.FileDescriptor;
The Android Open Source Project792a2202009-03-03 19:32:30 -080056import java.io.IOException;
Marco Nelissen39888902010-03-02 10:27:05 -080057import java.io.PrintWriter;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070058import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080059import java.util.Random;
60import java.util.Vector;
61
62/**
63 * Provides "background" audio playback capabilities, allowing the
64 * user to switch between activities without stopping playback.
65 */
66public class MediaPlaybackService extends Service {
67 /** used to specify whether enqueue() should start playing
68 * the new list of files right away, next or once all the currently
69 * queued files have been played
70 */
71 public static final int NOW = 1;
72 public static final int NEXT = 2;
73 public static final int LAST = 3;
74 public static final int PLAYBACKSERVICE_STATUS = 1;
75
76 public static final int SHUFFLE_NONE = 0;
77 public static final int SHUFFLE_NORMAL = 1;
78 public static final int SHUFFLE_AUTO = 2;
79
80 public static final int REPEAT_NONE = 0;
81 public static final int REPEAT_CURRENT = 1;
82 public static final int REPEAT_ALL = 2;
83
84 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
85 public static final String META_CHANGED = "com.android.music.metachanged";
86 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
The Android Open Source Project792a2202009-03-03 19:32:30 -080087
88 public static final String SERVICECMD = "com.android.music.musicservicecommand";
89 public static final String CMDNAME = "command";
90 public static final String CMDTOGGLEPAUSE = "togglepause";
91 public static final String CMDSTOP = "stop";
92 public static final String CMDPAUSE = "pause";
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -070093 public static final String CMDPLAY = "play";
The Android Open Source Project792a2202009-03-03 19:32:30 -080094 public static final String CMDPREVIOUS = "previous";
95 public static final String CMDNEXT = "next";
96
97 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
98 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
99 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
100 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
101
The Android Open Source Project792a2202009-03-03 19:32:30 -0800102 private static final int TRACK_ENDED = 1;
103 private static final int RELEASE_WAKELOCK = 2;
104 private static final int SERVER_DIED = 3;
Marco Nelissen7181da82010-12-01 16:39:04 -0800105 private static final int FOCUSCHANGE = 4;
Marco Nelissen2da473d2010-09-29 16:11:12 -0700106 private static final int FADEDOWN = 5;
107 private static final int FADEUP = 6;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700108 private static final int MAX_HISTORY_SIZE = 100;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800109
110 private MultiPlayer mPlayer;
111 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800112 private int mShuffleMode = SHUFFLE_NONE;
113 private int mRepeatMode = REPEAT_NONE;
114 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700115 private long [] mAutoShuffleList = null;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700116 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800117 private int mPlayListLen = 0;
118 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
119 private Cursor mCursor;
120 private int mPlayPos = -1;
121 private static final String LOGTAG = "MediaPlaybackService";
122 private final Shuffler mRand = new Shuffler();
123 private int mOpenFailedCounter = 0;
124 String[] mCursorCols = new String[] {
125 "audio._id AS _id", // index must match IDCOLIDX below
126 MediaStore.Audio.Media.ARTIST,
127 MediaStore.Audio.Media.ALBUM,
128 MediaStore.Audio.Media.TITLE,
129 MediaStore.Audio.Media.DATA,
130 MediaStore.Audio.Media.MIME_TYPE,
131 MediaStore.Audio.Media.ALBUM_ID,
132 MediaStore.Audio.Media.ARTIST_ID,
133 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
134 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
135 };
136 private final static int IDCOLIDX = 0;
137 private final static int PODCASTCOLIDX = 8;
138 private final static int BOOKMARKCOLIDX = 9;
139 private BroadcastReceiver mUnmountReceiver = null;
140 private WakeLock mWakeLock;
141 private int mServiceStartId = -1;
142 private boolean mServiceInUse = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700143 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800144 private boolean mQuietMode = false;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800145 private AudioManager mAudioManager;
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700146 private boolean mQueueIsSaveable = true;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800147 // used to track what type of audio focus loss caused the playback to pause
148 private boolean mPausedByTransientLossOfFocus = false;
149
The Android Open Source Project792a2202009-03-03 19:32:30 -0800150 private SharedPreferences mPreferences;
151 // We use this to distinguish between different cards when saving/restoring playlists.
152 // This will have to change if we want to support multiple simultaneous cards.
153 private int mCardId;
154
The Android Open Source Project490384b2009-03-11 12:11:59 -0700155 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800156
157 // interval after which we stop the service when idle
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700158 private static final int IDLE_DELAY = 60000;
Marco Nelissen8717d342011-09-08 09:32:51 -0700159
160 private RemoteControlClient mRemoteControlClient;
161
The Android Open Source Project792a2202009-03-03 19:32:30 -0800162 private Handler mMediaplayerHandler = new Handler() {
163 float mCurrentVolume = 1.0f;
164 @Override
165 public void handleMessage(Message msg) {
Marco Nelissen39888902010-03-02 10:27:05 -0800166 MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800167 switch (msg.what) {
Marco Nelissen2da473d2010-09-29 16:11:12 -0700168 case FADEDOWN:
169 mCurrentVolume -= .05f;
170 if (mCurrentVolume > .2f) {
171 mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
172 } else {
173 mCurrentVolume = .2f;
174 }
175 mPlayer.setVolume(mCurrentVolume);
176 break;
177 case FADEUP:
178 mCurrentVolume += .01f;
179 if (mCurrentVolume < 1.0f) {
180 mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 10);
181 } else {
182 mCurrentVolume = 1.0f;
183 }
184 mPlayer.setVolume(mCurrentVolume);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800185 break;
186 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700187 if (mIsSupposedToBePlaying) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800188 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800189 } else {
190 // the server died when we were idle, so just
191 // reopen the same song (it will start again
192 // from the beginning though when the user
193 // restarts)
194 openCurrent();
195 }
196 break;
197 case TRACK_ENDED:
198 if (mRepeatMode == REPEAT_CURRENT) {
199 seek(0);
200 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800201 } else {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800202 gotoNext(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800203 }
204 break;
205 case RELEASE_WAKELOCK:
206 mWakeLock.release();
207 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800208
209 case FOCUSCHANGE:
210 // This code is here so we can better synchronize it with the code that
211 // handles fade-in
212 switch (msg.arg1) {
213 case AudioManager.AUDIOFOCUS_LOSS:
214 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
215 if(isPlaying()) {
216 mPausedByTransientLossOfFocus = false;
217 }
218 pause();
219 break;
Marco Nelissen7181da82010-12-01 16:39:04 -0800220 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800221 mMediaplayerHandler.removeMessages(FADEUP);
222 mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
223 break;
224 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Marco Nelissen7181da82010-12-01 16:39:04 -0800225 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
226 if(isPlaying()) {
227 mPausedByTransientLossOfFocus = true;
228 }
229 pause();
230 break;
231 case AudioManager.AUDIOFOCUS_GAIN:
232 Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
233 if(!isPlaying() && mPausedByTransientLossOfFocus) {
234 mPausedByTransientLossOfFocus = false;
235 mCurrentVolume = 0f;
236 mPlayer.setVolume(mCurrentVolume);
237 play(); // also queues a fade-in
238 } else {
Marco Nelissen7bae28f2010-12-02 10:04:54 -0800239 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -0800240 mMediaplayerHandler.sendEmptyMessage(FADEUP);
241 }
242 break;
243 default:
244 Log.e(LOGTAG, "Unknown audio focus change code");
245 }
246 break;
247
The Android Open Source Project792a2202009-03-03 19:32:30 -0800248 default:
249 break;
250 }
251 }
252 };
253
254 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
255 @Override
256 public void onReceive(Context context, Intent intent) {
257 String action = intent.getAction();
258 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800259 MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800260 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800261 gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800262 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
263 prev();
264 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
265 if (isPlaying()) {
266 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700267 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800268 } else {
269 play();
270 }
271 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
272 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700273 mPausedByTransientLossOfFocus = false;
Marco Nelissenfc1c0dc2010-10-25 11:08:36 -0700274 } else if (CMDPLAY.equals(cmd)) {
275 play();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800276 } else if (CMDSTOP.equals(cmd)) {
277 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700278 mPausedByTransientLossOfFocus = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800279 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700280 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
281 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800282 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700283 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
284 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800285 }
286 }
287 };
288
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800289 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
Jean-Michel Trivif4cfdfd2010-03-31 12:12:23 -0700290 public void onAudioFocusChange(int focusChange) {
Marco Nelissen7181da82010-12-01 16:39:04 -0800291 mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800292 }
293 };
294
The Android Open Source Project792a2202009-03-03 19:32:30 -0800295 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800296 }
297
298 @Override
299 public void onCreate() {
300 super.onCreate();
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800301
302 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Marco Nelissen8717d342011-09-08 09:32:51 -0700303 ComponentName rec = new ComponentName(getPackageName(),
304 MediaButtonIntentReceiver.class.getName());
305 mAudioManager.registerMediaButtonEventReceiver(rec);
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700306 // TODO update to new constructor
307// mRemoteControlClient = new RemoteControlClient(rec);
308// mAudioManager.registerRemoteControlClient(mRemoteControlClient);
309//
310// int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
311// | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
312// | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
313// | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
314// | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
315// | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
316// mRemoteControlClient.setTransportControlFlags(flags);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800317
318 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800319 mCardId = MusicUtils.getCardId(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800320
321 registerExternalStorageListener();
322
323 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
324 mPlayer = new MultiPlayer();
325 mPlayer.setHandler(mMediaplayerHandler);
326
The Android Open Source Project792a2202009-03-03 19:32:30 -0800327 reloadQueue();
Marco Nelissen8717d342011-09-08 09:32:51 -0700328 notifyChange(QUEUE_CHANGED);
329 notifyChange(META_CHANGED);
330
The Android Open Source Project792a2202009-03-03 19:32:30 -0800331 IntentFilter commandFilter = new IntentFilter();
332 commandFilter.addAction(SERVICECMD);
333 commandFilter.addAction(TOGGLEPAUSE_ACTION);
334 commandFilter.addAction(PAUSE_ACTION);
335 commandFilter.addAction(NEXT_ACTION);
336 commandFilter.addAction(PREVIOUS_ACTION);
337 registerReceiver(mIntentReceiver, commandFilter);
338
The Android Open Source Project792a2202009-03-03 19:32:30 -0800339 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
340 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
341 mWakeLock.setReferenceCounted(false);
342
343 // If the service was idle, but got killed before it stopped itself, the
344 // system will relaunch it. Make sure it gets stopped again in that case.
345 Message msg = mDelayedStopHandler.obtainMessage();
346 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
347 }
348
349 @Override
350 public void onDestroy() {
351 // Check that we're not being destroyed while something is still playing.
352 if (isPlaying()) {
Marco Nelissene99341f2009-11-11 11:13:51 -0800353 Log.e(LOGTAG, "Service being destroyed while still playing.");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800354 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700355 // release all MediaPlayer resources, including the native player and wakelocks
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700356 Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
357 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
358 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
359 sendBroadcast(i);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700360 mPlayer.release();
361 mPlayer = null;
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -0800362
363 mAudioManager.abandonAudioFocus(mAudioFocusListener);
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700364 //mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800365
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700366 // make sure there aren't any other messages coming
367 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700368 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700369
The Android Open Source Project792a2202009-03-03 19:32:30 -0800370 if (mCursor != null) {
371 mCursor.close();
372 mCursor = null;
373 }
374
375 unregisterReceiver(mIntentReceiver);
376 if (mUnmountReceiver != null) {
377 unregisterReceiver(mUnmountReceiver);
378 mUnmountReceiver = null;
379 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800380 mWakeLock.release();
381 super.onDestroy();
382 }
383
384 private final char hexdigits [] = new char [] {
385 '0', '1', '2', '3',
386 '4', '5', '6', '7',
387 '8', '9', 'a', 'b',
388 'c', 'd', 'e', 'f'
389 };
390
391 private void saveQueue(boolean full) {
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700392 if (!mQueueIsSaveable) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800393 return;
394 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700395
The Android Open Source Project792a2202009-03-03 19:32:30 -0800396 Editor ed = mPreferences.edit();
397 //long start = System.currentTimeMillis();
398 if (full) {
399 StringBuilder q = new StringBuilder();
400
401 // The current playlist is saved as a list of "reverse hexadecimal"
402 // numbers, which we can generate faster than normal decimal or
403 // hexadecimal numbers, which in turn allows us to save the playlist
404 // more often without worrying too much about performance.
405 // (saving the full state takes about 40 ms under no-load conditions
406 // on the phone)
407 int len = mPlayListLen;
408 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700409 long n = mPlayList[i];
Marco Nelissen13355022010-08-31 15:13:27 -0700410 if (n < 0) {
411 continue;
412 } else if (n == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800413 q.append("0;");
414 } else {
415 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700416 int digit = (int)(n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700417 n >>>= 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800418 q.append(hexdigits[digit]);
419 }
420 q.append(";");
421 }
422 }
423 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
424 ed.putString("queue", q.toString());
425 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700426 if (mShuffleMode != SHUFFLE_NONE) {
427 // In shuffle mode we need to save the history too
428 len = mHistory.size();
429 q.setLength(0);
430 for (int i = 0; i < len; i++) {
431 int n = mHistory.get(i);
432 if (n == 0) {
433 q.append("0;");
434 } else {
435 while (n != 0) {
436 int digit = (n & 0xf);
Marco Nelissen13355022010-08-31 15:13:27 -0700437 n >>>= 4;
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700438 q.append(hexdigits[digit]);
439 }
440 q.append(";");
441 }
442 }
443 ed.putString("history", q.toString());
444 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800445 }
446 ed.putInt("curpos", mPlayPos);
447 if (mPlayer.isInitialized()) {
448 ed.putLong("seekpos", mPlayer.position());
449 }
450 ed.putInt("repeatmode", mRepeatMode);
451 ed.putInt("shufflemode", mShuffleMode);
Brad Fitzpatrick14c3cae2010-09-10 09:38:57 -0700452 SharedPreferencesCompat.apply(ed);
453
The Android Open Source Project792a2202009-03-03 19:32:30 -0800454 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
455 }
456
457 private void reloadQueue() {
458 String q = null;
459
460 boolean newstyle = false;
461 int id = mCardId;
462 if (mPreferences.contains("cardid")) {
463 newstyle = true;
464 id = mPreferences.getInt("cardid", ~mCardId);
465 }
466 if (id == mCardId) {
467 // Only restore the saved playlist if the card is still
468 // the same one as when the playlist was saved
469 q = mPreferences.getString("queue", "");
470 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700471 int qlen = q != null ? q.length() : 0;
472 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800473 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700474 int plen = 0;
475 int n = 0;
476 int shift = 0;
477 for (int i = 0; i < qlen; i++) {
478 char c = q.charAt(i);
479 if (c == ';') {
480 ensurePlayListCapacity(plen + 1);
481 mPlayList[plen] = n;
482 plen++;
483 n = 0;
484 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800485 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700486 if (c >= '0' && c <= '9') {
487 n += ((c - '0') << shift);
488 } else if (c >= 'a' && c <= 'f') {
489 n += ((10 + c - 'a') << shift);
490 } else {
491 // bogus playlist data
492 plen = 0;
493 break;
494 }
495 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800496 }
497 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700498 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800499
500 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700501 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800502 // The saved playlist is bogus, discard it
503 mPlayListLen = 0;
504 return;
505 }
506 mPlayPos = pos;
507
508 // When reloadQueue is called in response to a card-insertion,
509 // we might not be able to query the media provider right away.
510 // To deal with this, try querying for the current file, and if
511 // that fails, wait a while and try again. If that too fails,
512 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700513 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800514 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
515 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700516 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800517 // wait a bit and try again
518 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700519 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800520 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
521 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
522 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700523 if (crsr != null) {
524 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800525 }
526
527 // Make sure we don't auto-skip to the next song, since that
528 // also starts playback. What could happen in that case is:
529 // - music is paused
530 // - go to UMS and delete some files, including the currently playing one
531 // - come back from UMS
532 // (time passes)
533 // - music app is killed for some reason (out of memory)
534 // - music service is restarted, service restores state, doesn't find
535 // the "current" file, goes to the next and: playback starts on its
536 // own, potentially at some random inconvenient time.
537 mOpenFailedCounter = 20;
538 mQuietMode = true;
539 openCurrent();
540 mQuietMode = false;
541 if (!mPlayer.isInitialized()) {
542 // couldn't restore the saved state
543 mPlayListLen = 0;
544 return;
545 }
546
547 long seekpos = mPreferences.getLong("seekpos", 0);
548 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
Marco Nelissene99341f2009-11-11 11:13:51 -0800549 Log.d(LOGTAG, "restored queue, currently at position "
550 + position() + "/" + duration()
551 + " (requested " + seekpos + ")");
The Android Open Source Project792a2202009-03-03 19:32:30 -0800552
553 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
554 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
555 repmode = REPEAT_NONE;
556 }
557 mRepeatMode = repmode;
558
559 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
560 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
561 shufmode = SHUFFLE_NONE;
562 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700563 if (shufmode != SHUFFLE_NONE) {
564 // in shuffle mode we need to restore the history too
565 q = mPreferences.getString("history", "");
566 qlen = q != null ? q.length() : 0;
567 if (qlen > 1) {
568 plen = 0;
569 n = 0;
570 shift = 0;
571 mHistory.clear();
572 for (int i = 0; i < qlen; i++) {
573 char c = q.charAt(i);
574 if (c == ';') {
575 if (n >= mPlayListLen) {
576 // bogus history data
577 mHistory.clear();
578 break;
579 }
580 mHistory.add(n);
581 n = 0;
582 shift = 0;
583 } else {
584 if (c >= '0' && c <= '9') {
585 n += ((c - '0') << shift);
586 } else if (c >= 'a' && c <= 'f') {
587 n += ((10 + c - 'a') << shift);
588 } else {
589 // bogus history data
590 mHistory.clear();
591 break;
592 }
593 shift += 4;
594 }
595 }
596 }
597 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800598 if (shufmode == SHUFFLE_AUTO) {
599 if (! makeAutoShuffleList()) {
600 shufmode = SHUFFLE_NONE;
601 }
602 }
603 mShuffleMode = shufmode;
604 }
605 }
606
607 @Override
608 public IBinder onBind(Intent intent) {
609 mDelayedStopHandler.removeCallbacksAndMessages(null);
610 mServiceInUse = true;
611 return mBinder;
612 }
613
614 @Override
615 public void onRebind(Intent intent) {
616 mDelayedStopHandler.removeCallbacksAndMessages(null);
617 mServiceInUse = true;
618 }
619
620 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700621 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800622 mServiceStartId = startId;
623 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700624
625 if (intent != null) {
626 String action = intent.getAction();
627 String cmd = intent.getStringExtra("command");
Marco Nelissen39888902010-03-02 10:27:05 -0800628 MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700629
630 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -0800631 gotoNext(true);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700632 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
Marco Nelissenb63b5d12009-11-18 16:04:19 -0800633 if (position() < 2000) {
634 prev();
635 } else {
636 seek(0);
637 play();
638 }
Marco Nelissenc1017e52009-09-24 13:21:06 -0700639 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
640 if (isPlaying()) {
641 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700642 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700643 } else {
644 play();
645 }
646 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800647 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700648 mPausedByTransientLossOfFocus = false;
Jaikumar Ganesh47dd8472010-12-16 15:51:31 -0800649 } else if (CMDPLAY.equals(cmd)) {
650 play();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700651 } else if (CMDSTOP.equals(cmd)) {
652 pause();
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700653 mPausedByTransientLossOfFocus = false;
Marco Nelissenc1017e52009-09-24 13:21:06 -0700654 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800655 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800656 }
657
658 // make sure the service will shut down on its own if it was
659 // just started but not bound to and nothing is playing
660 mDelayedStopHandler.removeCallbacksAndMessages(null);
661 Message msg = mDelayedStopHandler.obtainMessage();
662 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700663 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800664 }
665
666 @Override
667 public boolean onUnbind(Intent intent) {
668 mServiceInUse = false;
669
670 // Take a snapshot of the current playlist
671 saveQueue(true);
672
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700673 if (isPlaying() || mPausedByTransientLossOfFocus) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800674 // something is currently playing, or will be playing once
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700675 // an in-progress action requesting audio focus ends, so don't stop the service now.
The Android Open Source Project792a2202009-03-03 19:32:30 -0800676 return true;
677 }
678
679 // If there is a playlist but playback is paused, then wait a while
680 // before stopping the service, so that pause/resume isn't slow.
681 // Also delay stopping the service if we're transitioning between tracks.
682 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
683 Message msg = mDelayedStopHandler.obtainMessage();
684 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
685 return true;
686 }
687
688 // No active playlist, OK to stop the service right now
689 stopSelf(mServiceStartId);
690 return true;
691 }
692
693 private Handler mDelayedStopHandler = new Handler() {
694 @Override
695 public void handleMessage(Message msg) {
696 // Check again to make sure nothing is playing right now
Jean-Michel Triviaa331872010-04-20 17:00:26 -0700697 if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
The Android Open Source Project792a2202009-03-03 19:32:30 -0800698 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
699 return;
700 }
701 // save the queue again, because it might have changed
702 // since the user exited the music app (because of
703 // party-shuffle or because the play-position changed)
704 saveQueue(true);
705 stopSelf(mServiceStartId);
706 }
707 };
Marco Nelissenf2ef3b52010-09-21 15:47:27 -0700708
The Android Open Source Project792a2202009-03-03 19:32:30 -0800709 /**
710 * Called when we receive a ACTION_MEDIA_EJECT notification.
711 *
712 * @param storagePath path to mount point for the removed media
713 */
714 public void closeExternalStorageFiles(String storagePath) {
715 // stop playback and clean up if the SD card is going to be unmounted.
716 stop(true);
717 notifyChange(QUEUE_CHANGED);
718 notifyChange(META_CHANGED);
719 }
720
721 /**
722 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
723 * The intent will call closeExternalStorageFiles() if the external media
724 * is going to be ejected, so applications can clean up any files they have open.
725 */
726 public void registerExternalStorageListener() {
727 if (mUnmountReceiver == null) {
728 mUnmountReceiver = new BroadcastReceiver() {
729 @Override
730 public void onReceive(Context context, Intent intent) {
731 String action = intent.getAction();
732 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
733 saveQueue(true);
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700734 mQueueIsSaveable = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800735 closeExternalStorageFiles(intent.getData().getPath());
736 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
737 mMediaMountedCount++;
Marco Nelissen87bbf3c2009-12-23 16:18:45 -0800738 mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800739 reloadQueue();
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700740 mQueueIsSaveable = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800741 notifyChange(QUEUE_CHANGED);
742 notifyChange(META_CHANGED);
743 }
744 }
745 };
746 IntentFilter iFilter = new IntentFilter();
747 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
748 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
749 iFilter.addDataScheme("file");
750 registerReceiver(mUnmountReceiver, iFilter);
751 }
752 }
753
754 /**
755 * Notify the change-receivers that something has changed.
756 * The intent that is sent contains the following data
757 * for the currently playing track:
758 * "id" - Integer: the database row ID
759 * "artist" - String: the name of the artist
760 * "album" - String: the name of the album
761 * "track" - String: the name of the track
762 * The intent has an action that is one of
763 * "com.android.music.metachanged"
764 * "com.android.music.queuechanged",
765 * "com.android.music.playbackcomplete"
766 * "com.android.music.playstatechanged"
767 * respectively indicating that a new track has
768 * started playing, that the playback queue has
769 * changed, that playback has stopped because
770 * the last file in the list has been played,
771 * or that the play-state changed (paused/resumed).
772 */
773 private void notifyChange(String what) {
Marco Nelissen8717d342011-09-08 09:32:51 -0700774
The Android Open Source Project792a2202009-03-03 19:32:30 -0800775 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700776 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800777 i.putExtra("artist", getArtistName());
778 i.putExtra("album",getAlbumName());
779 i.putExtra("track", getTrackName());
Marco Nelissen6b507de2010-10-20 14:08:10 -0700780 i.putExtra("playing", isPlaying());
781 sendStickyBroadcast(i);
Marco Nelissen8717d342011-09-08 09:32:51 -0700782
783 if (what.equals(PLAYSTATE_CHANGED)) {
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700784// mRemoteControlClient.setPlaybackState(isPlaying() ?
785// RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
Marco Nelissen8717d342011-09-08 09:32:51 -0700786 } else if (what.equals(META_CHANGED)) {
Jean-Michel Trivi94ed2422011-09-13 18:27:19 -0700787// RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
788// ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
789// ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
790// ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
791// ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
792// Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), false);
793// if (b != null) {
794// ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
795// }
796// ed.apply();
Marco Nelissen8717d342011-09-08 09:32:51 -0700797 }
798
The Android Open Source Project792a2202009-03-03 19:32:30 -0800799 if (what.equals(QUEUE_CHANGED)) {
800 saveQueue(true);
801 } else {
802 saveQueue(false);
803 }
804
The Android Open Source Project490384b2009-03-11 12:11:59 -0700805 // Share this notification directly with our widgets
806 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800807 }
808
809 private void ensurePlayListCapacity(int size) {
810 if (mPlayList == null || size > mPlayList.length) {
811 // reallocate at 2x requested size so we don't
812 // need to grow and copy the array for every
813 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700814 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700815 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800816 for (int i = 0; i < len; i++) {
817 newlist[i] = mPlayList[i];
818 }
819 mPlayList = newlist;
820 }
821 // FIXME: shrink the array when the needed size is much smaller
822 // than the allocated size
823 }
824
825 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700826 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800827 int addlen = list.length;
828 if (position < 0) { // overwrite
829 mPlayListLen = 0;
830 position = 0;
831 }
832 ensurePlayListCapacity(mPlayListLen + addlen);
833 if (position > mPlayListLen) {
834 position = mPlayListLen;
835 }
836
837 // move part of list after insertion point
838 int tailsize = mPlayListLen - position;
839 for (int i = tailsize ; i > 0 ; i--) {
840 mPlayList[position + i] = mPlayList[position + i - addlen];
841 }
842
843 // copy list into playlist
844 for (int i = 0; i < addlen; i++) {
845 mPlayList[position + i] = list[i];
846 }
847 mPlayListLen += addlen;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -0700848 if (mPlayListLen == 0) {
849 mCursor.close();
850 mCursor = null;
851 notifyChange(META_CHANGED);
852 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800853 }
854
855 /**
856 * Appends a list of tracks to the current playlist.
857 * If nothing is playing currently, playback will be started at
858 * the first track.
859 * If the action is NOW, playback will switch to the first of
860 * the new tracks immediately.
861 * @param list The list of tracks to append.
862 * @param action NOW, NEXT or LAST
863 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700864 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800865 synchronized(this) {
866 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
867 addToPlayList(list, mPlayPos + 1);
868 notifyChange(QUEUE_CHANGED);
869 } else {
870 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
871 addToPlayList(list, Integer.MAX_VALUE);
872 notifyChange(QUEUE_CHANGED);
873 if (action == NOW) {
874 mPlayPos = mPlayListLen - list.length;
875 openCurrent();
876 play();
877 notifyChange(META_CHANGED);
878 return;
879 }
880 }
881 if (mPlayPos < 0) {
882 mPlayPos = 0;
883 openCurrent();
884 play();
885 notifyChange(META_CHANGED);
886 }
887 }
888 }
889
890 /**
891 * Replaces the current playlist with a new list,
892 * and prepares for starting playback at the specified
893 * position in the list, or a random position if the
894 * specified position is 0.
895 * @param list The new list of tracks.
896 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700897 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800898 synchronized (this) {
899 if (mShuffleMode == SHUFFLE_AUTO) {
900 mShuffleMode = SHUFFLE_NORMAL;
901 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700902 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800903 int listlength = list.length;
904 boolean newlist = true;
905 if (mPlayListLen == listlength) {
906 // possible fast path: list might be the same
907 newlist = false;
908 for (int i = 0; i < listlength; i++) {
909 if (list[i] != mPlayList[i]) {
910 newlist = true;
911 break;
912 }
913 }
914 }
915 if (newlist) {
916 addToPlayList(list, -1);
917 notifyChange(QUEUE_CHANGED);
918 }
919 int oldpos = mPlayPos;
920 if (position >= 0) {
921 mPlayPos = position;
922 } else {
923 mPlayPos = mRand.nextInt(mPlayListLen);
924 }
925 mHistory.clear();
926
927 saveBookmarkIfNeeded();
928 openCurrent();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700929 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800930 notifyChange(META_CHANGED);
931 }
932 }
933 }
934
935 /**
936 * Moves the item at index1 to index2.
937 * @param index1
938 * @param index2
939 */
940 public void moveQueueItem(int index1, int index2) {
941 synchronized (this) {
942 if (index1 >= mPlayListLen) {
943 index1 = mPlayListLen - 1;
944 }
945 if (index2 >= mPlayListLen) {
946 index2 = mPlayListLen - 1;
947 }
948 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700949 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800950 for (int i = index1; i < index2; i++) {
951 mPlayList[i] = mPlayList[i+1];
952 }
953 mPlayList[index2] = tmp;
954 if (mPlayPos == index1) {
955 mPlayPos = index2;
956 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
957 mPlayPos--;
958 }
959 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700960 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800961 for (int i = index1; i > index2; i--) {
962 mPlayList[i] = mPlayList[i-1];
963 }
964 mPlayList[index2] = tmp;
965 if (mPlayPos == index1) {
966 mPlayPos = index2;
967 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
968 mPlayPos++;
969 }
970 }
971 notifyChange(QUEUE_CHANGED);
972 }
973 }
974
975 /**
976 * Returns the current play list
977 * @return An array of integers containing the IDs of the tracks in the play list
978 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700979 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800980 synchronized (this) {
981 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700982 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800983 for (int i = 0; i < len; i++) {
984 list[i] = mPlayList[i];
985 }
986 return list;
987 }
988 }
989
990 private void openCurrent() {
991 synchronized (this) {
992 if (mCursor != null) {
993 mCursor.close();
994 mCursor = null;
995 }
Marco Nelissen8d08ec22010-05-10 14:05:24 -0700996
The Android Open Source Project792a2202009-03-03 19:32:30 -0800997 if (mPlayListLen == 0) {
998 return;
999 }
1000 stop(false);
1001
1002 String id = String.valueOf(mPlayList[mPlayPos]);
1003
1004 mCursor = getContentResolver().query(
1005 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1006 mCursorCols, "_id=" + id , null, null);
1007 if (mCursor != null) {
1008 mCursor.moveToFirst();
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001009 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001010 // go to bookmark if needed
1011 if (isPodcast()) {
1012 long bookmark = getBookmark();
1013 // Start playing a little bit before the bookmark,
1014 // so it's easier to get back in to the narrative.
1015 seek(bookmark - 5000);
1016 }
1017 }
1018 }
1019 }
1020
The Android Open Source Project792a2202009-03-03 19:32:30 -08001021 /**
1022 * Opens the specified file and readies it for playback.
1023 *
1024 * @param path The full path of the file to be opened.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001025 */
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001026 public void open(String path) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001027 synchronized (this) {
1028 if (path == null) {
1029 return;
1030 }
1031
The Android Open Source Project792a2202009-03-03 19:32:30 -08001032 // if mCursor is null, try to associate path with a database cursor
1033 if (mCursor == null) {
1034
1035 ContentResolver resolver = getContentResolver();
1036 Uri uri;
1037 String where;
1038 String selectionArgs[];
1039 if (path.startsWith("content://media/")) {
1040 uri = Uri.parse(path);
1041 where = null;
1042 selectionArgs = null;
1043 } else {
1044 uri = MediaStore.Audio.Media.getContentUriForPath(path);
1045 where = MediaStore.Audio.Media.DATA + "=?";
1046 selectionArgs = new String[] { path };
1047 }
1048
1049 try {
1050 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
1051 if (mCursor != null) {
1052 if (mCursor.getCount() == 0) {
1053 mCursor.close();
1054 mCursor = null;
1055 } else {
1056 mCursor.moveToNext();
1057 ensurePlayListCapacity(1);
1058 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -07001059 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001060 mPlayPos = 0;
1061 }
1062 }
1063 } catch (UnsupportedOperationException ex) {
1064 }
1065 }
1066 mFileToPlay = path;
1067 mPlayer.setDataSource(mFileToPlay);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001068 if (! mPlayer.isInitialized()) {
1069 stop(true);
1070 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1071 // beware: this ends up being recursive because next() calls open() again.
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001072 gotoNext(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001073 }
1074 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1075 // need to make sure we only shows this once
1076 mOpenFailedCounter = 0;
1077 if (!mQuietMode) {
1078 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1079 }
Marco Nelissene99341f2009-11-11 11:13:51 -08001080 Log.d(LOGTAG, "Failed to open file for playback");
The Android Open Source Project792a2202009-03-03 19:32:30 -08001081 }
1082 } else {
1083 mOpenFailedCounter = 0;
1084 }
1085 }
1086 }
1087
1088 /**
1089 * Starts playback of a previously opened file.
1090 */
1091 public void play() {
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001092 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
1093 AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivi3d22fc22010-03-17 11:46:58 -07001094 mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
1095 MediaButtonIntentReceiver.class.getName()));
Jean-Michel Trivi085cbfd2010-03-08 17:06:18 -08001096
The Android Open Source Project792a2202009-03-03 19:32:30 -08001097 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001098 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001099 long duration = mPlayer.duration();
1100 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1101 mPlayer.position() >= duration - 2000) {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001102 gotoNext(true);
Thomas Tuttle272eb782009-01-28 21:06:46 -05001103 }
1104
The Android Open Source Project792a2202009-03-03 19:32:30 -08001105 mPlayer.start();
Marco Nelissen7181da82010-12-01 16:39:04 -08001106 // make sure we fade in, in case a previous fadein was stopped because
1107 // of another focus loss
Marco Nelissen7bae28f2010-12-02 10:04:54 -08001108 mMediaplayerHandler.removeMessages(FADEDOWN);
Marco Nelissen7181da82010-12-01 16:39:04 -08001109 mMediaplayerHandler.sendEmptyMessage(FADEUP);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001110
The Android Open Source Project792a2202009-03-03 19:32:30 -08001111 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1112 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1113 if (getAudioId() < 0) {
1114 // streaming
1115 views.setTextViewText(R.id.trackname, getPath());
1116 views.setTextViewText(R.id.artistalbum, null);
1117 } else {
1118 String artist = getArtistName();
1119 views.setTextViewText(R.id.trackname, getTrackName());
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001120 if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001121 artist = getString(R.string.unknown_artist_name);
1122 }
1123 String album = getAlbumName();
Marco Nelissenf4d4b342010-01-04 15:01:18 -08001124 if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001125 album = getString(R.string.unknown_album_name);
1126 }
1127
1128 views.setTextViewText(R.id.artistalbum,
1129 getString(R.string.notification_artist_album, artist, album)
1130 );
1131 }
1132
The Android Open Source Project792a2202009-03-03 19:32:30 -08001133 Notification status = new Notification();
1134 status.contentView = views;
1135 status.flags |= Notification.FLAG_ONGOING_EVENT;
1136 status.icon = R.drawable.stat_notify_musicplayer;
1137 status.contentIntent = PendingIntent.getActivity(this, 0,
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001138 new Intent("com.android.music.PLAYBACK_VIEWER")
1139 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001140 startForeground(PLAYBACKSERVICE_STATUS, status);
Marco Nelissenc1333372009-05-06 12:54:47 -07001141 if (!mIsSupposedToBePlaying) {
Mike Cleron347fe572009-10-09 12:26:28 -07001142 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001143 notifyChange(PLAYSTATE_CHANGED);
1144 }
Mike Cleron347fe572009-10-09 12:26:28 -07001145
The Android Open Source Project792a2202009-03-03 19:32:30 -08001146 } else if (mPlayListLen <= 0) {
1147 // This is mostly so that if you press 'play' on a bluetooth headset
1148 // without every having played anything before, it will still play
1149 // something.
1150 setShuffleMode(SHUFFLE_AUTO);
1151 }
1152 }
1153
1154 private void stop(boolean remove_status_icon) {
1155 if (mPlayer.isInitialized()) {
1156 mPlayer.stop();
1157 }
1158 mFileToPlay = null;
1159 if (mCursor != null) {
1160 mCursor.close();
1161 mCursor = null;
1162 }
1163 if (remove_status_icon) {
1164 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001165 } else {
1166 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001167 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001168 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001169 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001170 }
1171 }
1172
1173 /**
1174 * Stops playback.
1175 */
1176 public void stop() {
1177 stop(true);
1178 }
1179
1180 /**
1181 * Pauses playback (call play() to resume)
1182 */
1183 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001184 synchronized(this) {
Marco Nelissen7181da82010-12-01 16:39:04 -08001185 mMediaplayerHandler.removeMessages(FADEUP);
Marco Nelissen407cf912009-05-11 09:56:31 -07001186 if (isPlaying()) {
1187 mPlayer.pause();
1188 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001189 mIsSupposedToBePlaying = false;
1190 notifyChange(PLAYSTATE_CHANGED);
1191 saveBookmarkIfNeeded();
1192 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001193 }
1194 }
1195
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001196 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001197 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001198 * @return true if something is playing (or will be playing shortly, in case
1199 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001200 */
1201 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001202 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001203 }
1204
1205 /*
1206 Desired behavior for prev/next/shuffle:
1207
1208 - NEXT will move to the next track in the list when not shuffling, and to
1209 a track randomly picked from the not-yet-played tracks when shuffling.
1210 If all tracks have already been played, pick from the full set, but
1211 avoid picking the previously played track if possible.
1212 - when shuffling, PREV will go to the previously played track. Hitting PREV
1213 again will go to the track played before that, etc. When the start of the
1214 history has been reached, PREV is a no-op.
1215 When not shuffling, PREV will go to the sequentially previous track (the
1216 difference with the shuffle-case is mainly that when not shuffling, the
1217 user can back up to tracks that are not in the history).
1218
1219 Example:
1220 When playing an album with 10 tracks from the start, and enabling shuffle
1221 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1222 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1223 When hitting 'prev' 8 times while playing track 7 in this example, the
1224 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1225 a random track will be picked again. If at any time user disables shuffling
1226 the next/previous track will be picked in sequential order again.
1227 */
1228
1229 public void prev() {
1230 synchronized (this) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001231 if (mShuffleMode == SHUFFLE_NORMAL) {
1232 // go to previously-played track and remove it from the history
1233 int histsize = mHistory.size();
1234 if (histsize == 0) {
1235 // prev is a no-op
1236 return;
1237 }
1238 Integer pos = mHistory.remove(histsize - 1);
1239 mPlayPos = pos.intValue();
1240 } else {
1241 if (mPlayPos > 0) {
1242 mPlayPos--;
1243 } else {
1244 mPlayPos = mPlayListLen - 1;
1245 }
1246 }
1247 saveBookmarkIfNeeded();
1248 stop(false);
1249 openCurrent();
1250 play();
1251 notifyChange(META_CHANGED);
1252 }
1253 }
1254
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001255 /**
1256 * Get the next position to play. Note that this may actually modify mPlayPos
1257 * if playback is in SHUFFLE_AUTO mode and the shuffle list window needed to
1258 * be adjusted. Either way, the return value is the next value that should be
1259 * assigned to mPlayPos;
1260 */
1261 private int getNextPosition(boolean force) {
1262 if (mShuffleMode == SHUFFLE_NORMAL) {
1263 // Pick random next track from the not-yet-played ones
1264 // TODO: make it work right after adding/removing items in the queue.
1265
1266 // Store the current file in the history, but keep the history at a
1267 // reasonable size
1268 if (mPlayPos >= 0) {
1269 mHistory.add(mPlayPos);
1270 }
1271 if (mHistory.size() > MAX_HISTORY_SIZE) {
1272 mHistory.removeElementAt(0);
1273 }
1274
1275 int numTracks = mPlayListLen;
1276 int[] tracks = new int[numTracks];
1277 for (int i=0;i < numTracks; i++) {
1278 tracks[i] = i;
1279 }
1280
1281 int numHistory = mHistory.size();
1282 int numUnplayed = numTracks;
1283 for (int i=0;i < numHistory; i++) {
1284 int idx = mHistory.get(i).intValue();
1285 if (idx < numTracks && tracks[idx] >= 0) {
1286 numUnplayed--;
1287 tracks[idx] = -1;
1288 }
1289 }
1290
1291 // 'numUnplayed' now indicates how many tracks have not yet
1292 // been played, and 'tracks' contains the indices of those
1293 // tracks.
1294 if (numUnplayed <=0) {
1295 // everything's already been played
1296 if (mRepeatMode == REPEAT_ALL || force) {
1297 //pick from full set
1298 numUnplayed = numTracks;
1299 for (int i=0;i < numTracks; i++) {
1300 tracks[i] = i;
1301 }
1302 } else {
1303 // all done
1304 return -1;
1305 }
1306 }
1307 int skip = mRand.nextInt(numUnplayed);
1308 int cnt = -1;
1309 while (true) {
1310 while (tracks[++cnt] < 0)
1311 ;
1312 skip--;
1313 if (skip < 0) {
1314 break;
1315 }
1316 }
1317 return cnt;
1318 } else if (mShuffleMode == SHUFFLE_AUTO) {
1319 doAutoShuffleUpdate();
1320 return mPlayPos + 1;
1321 } else {
1322 if (mPlayPos >= mPlayListLen - 1) {
1323 // we're at the end of the list
1324 if (mRepeatMode == REPEAT_NONE && !force) {
1325 // all done
1326 return -1;
1327 } else if (mRepeatMode == REPEAT_ALL || force) {
1328 return 0;
1329 }
1330 return -1;
1331 } else {
1332 return mPlayPos + 1;
1333 }
1334 }
1335 }
1336
1337 public void gotoNext(boolean force) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001338 synchronized (this) {
Marco Nelissen663fea32009-06-12 13:39:27 -07001339 if (mPlayListLen <= 0) {
Marco Nelissene99341f2009-11-11 11:13:51 -08001340 Log.d(LOGTAG, "No play queue");
Marco Nelissen663fea32009-06-12 13:39:27 -07001341 return;
1342 }
1343
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001344 int pos = getNextPosition(force);
1345 if (pos < 0) {
1346 gotoIdleState();
1347 if (mIsSupposedToBePlaying) {
1348 mIsSupposedToBePlaying = false;
1349 notifyChange(PLAYSTATE_CHANGED);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001350 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001351 return;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001352 }
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001353 mPlayPos = pos;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001354 saveBookmarkIfNeeded();
1355 stop(false);
1356 openCurrent();
1357 play();
1358 notifyChange(META_CHANGED);
1359 }
1360 }
1361
1362 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001363 mDelayedStopHandler.removeCallbacksAndMessages(null);
1364 Message msg = mDelayedStopHandler.obtainMessage();
1365 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001366 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001367 }
1368
1369 private void saveBookmarkIfNeeded() {
1370 try {
1371 if (isPodcast()) {
1372 long pos = position();
1373 long bookmark = getBookmark();
1374 long duration = duration();
1375 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1376 (pos > bookmark && (pos - 10000) < bookmark)) {
1377 // The existing bookmark is close to the current
1378 // position, so don't update it.
1379 return;
1380 }
1381 if (pos < 15000 || (pos + 10000) > duration) {
1382 // if we're near the start or end, clear the bookmark
1383 pos = 0;
1384 }
1385
1386 // write 'pos' to the bookmark field
1387 ContentValues values = new ContentValues();
1388 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1389 Uri uri = ContentUris.withAppendedId(
1390 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1391 getContentResolver().update(uri, values, null, null);
1392 }
1393 } catch (SQLiteException ex) {
1394 }
1395 }
1396
1397 // Make sure there are at least 5 items after the currently playing item
1398 // and no more than 10 items before.
1399 private void doAutoShuffleUpdate() {
1400 boolean notify = false;
Marco Nelissen3f502de2010-09-28 15:07:29 -07001401
The Android Open Source Project792a2202009-03-03 19:32:30 -08001402 // remove old entries
1403 if (mPlayPos > 10) {
1404 removeTracks(0, mPlayPos - 9);
1405 notify = true;
1406 }
1407 // add new entries if needed
1408 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1409 for (int i = 0; i < to_add; i++) {
1410 // pick something at random from the list
Marco Nelissen3f502de2010-09-28 15:07:29 -07001411
1412 int lookback = mHistory.size();
1413 int idx = -1;
1414 while(true) {
1415 idx = mRand.nextInt(mAutoShuffleList.length);
1416 if (!wasRecentlyUsed(idx, lookback)) {
1417 break;
1418 }
1419 lookback /= 2;
1420 }
1421 mHistory.add(idx);
1422 if (mHistory.size() > MAX_HISTORY_SIZE) {
1423 mHistory.remove(0);
1424 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001425 ensurePlayListCapacity(mPlayListLen + 1);
Marco Nelissen3f502de2010-09-28 15:07:29 -07001426 mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001427 notify = true;
1428 }
1429 if (notify) {
1430 notifyChange(QUEUE_CHANGED);
1431 }
1432 }
1433
Marco Nelissen3f502de2010-09-28 15:07:29 -07001434 // check that the specified idx is not in the history (but only look at at
1435 // most lookbacksize entries in the history)
1436 private boolean wasRecentlyUsed(int idx, int lookbacksize) {
1437
1438 // early exit to prevent infinite loops in case idx == mPlayPos
1439 if (lookbacksize == 0) {
1440 return false;
1441 }
1442
1443 int histsize = mHistory.size();
1444 if (histsize < lookbacksize) {
1445 Log.d(LOGTAG, "lookback too big");
1446 lookbacksize = histsize;
1447 }
1448 int maxidx = histsize - 1;
1449 for (int i = 0; i < lookbacksize; i++) {
1450 long entry = mHistory.get(maxidx - i);
1451 if (entry == idx) {
1452 return true;
1453 }
1454 }
1455 return false;
1456 }
1457
The Android Open Source Project792a2202009-03-03 19:32:30 -08001458 // A simple variation of Random that makes sure that the
1459 // value it returns is not equal to the value it returned
1460 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001461 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001462 private int mPrevious;
1463 private Random mRandom = new Random();
1464 public int nextInt(int interval) {
1465 int ret;
1466 do {
1467 ret = mRandom.nextInt(interval);
1468 } while (ret == mPrevious && interval > 1);
1469 mPrevious = ret;
1470 return ret;
1471 }
1472 };
1473
1474 private boolean makeAutoShuffleList() {
1475 ContentResolver res = getContentResolver();
1476 Cursor c = null;
1477 try {
1478 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1479 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1480 null, null);
1481 if (c == null || c.getCount() == 0) {
1482 return false;
1483 }
1484 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001485 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001486 for (int i = 0; i < len; i++) {
1487 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001488 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001489 }
1490 mAutoShuffleList = list;
1491 return true;
1492 } catch (RuntimeException ex) {
1493 } finally {
1494 if (c != null) {
1495 c.close();
1496 }
1497 }
1498 return false;
1499 }
1500
1501 /**
1502 * Removes the range of tracks specified from the play list. If a file within the range is
1503 * the file currently being played, playback will move to the next file after the
1504 * range.
1505 * @param first The first file to be removed
1506 * @param last The last file to be removed
1507 * @return the number of tracks deleted
1508 */
1509 public int removeTracks(int first, int last) {
1510 int numremoved = removeTracksInternal(first, last);
1511 if (numremoved > 0) {
1512 notifyChange(QUEUE_CHANGED);
1513 }
1514 return numremoved;
1515 }
1516
1517 private int removeTracksInternal(int first, int last) {
1518 synchronized (this) {
1519 if (last < first) return 0;
1520 if (first < 0) first = 0;
1521 if (last >= mPlayListLen) last = mPlayListLen - 1;
1522
1523 boolean gotonext = false;
1524 if (first <= mPlayPos && mPlayPos <= last) {
1525 mPlayPos = first;
1526 gotonext = true;
1527 } else if (mPlayPos > last) {
1528 mPlayPos -= (last - first + 1);
1529 }
1530 int num = mPlayListLen - last - 1;
1531 for (int i = 0; i < num; i++) {
1532 mPlayList[first + i] = mPlayList[last + 1 + i];
1533 }
1534 mPlayListLen -= last - first + 1;
1535
1536 if (gotonext) {
1537 if (mPlayListLen == 0) {
1538 stop(true);
1539 mPlayPos = -1;
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001540 if (mCursor != null) {
1541 mCursor.close();
1542 mCursor = null;
1543 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001544 } else {
1545 if (mPlayPos >= mPlayListLen) {
1546 mPlayPos = 0;
1547 }
1548 boolean wasPlaying = isPlaying();
1549 stop(false);
1550 openCurrent();
1551 if (wasPlaying) {
1552 play();
1553 }
1554 }
Marco Nelissen3aa9ad02010-09-16 13:23:11 -07001555 notifyChange(META_CHANGED);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001556 }
1557 return last - first + 1;
1558 }
1559 }
1560
1561 /**
1562 * Removes all instances of the track with the given id
1563 * from the playlist.
1564 * @param id The id to be removed
1565 * @return how many instances of the track were removed
1566 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001567 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001568 int numremoved = 0;
1569 synchronized (this) {
1570 for (int i = 0; i < mPlayListLen; i++) {
1571 if (mPlayList[i] == id) {
1572 numremoved += removeTracksInternal(i, i);
1573 i--;
1574 }
1575 }
1576 }
1577 if (numremoved > 0) {
1578 notifyChange(QUEUE_CHANGED);
1579 }
1580 return numremoved;
1581 }
1582
1583 public void setShuffleMode(int shufflemode) {
1584 synchronized(this) {
1585 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1586 return;
1587 }
1588 mShuffleMode = shufflemode;
1589 if (mShuffleMode == SHUFFLE_AUTO) {
1590 if (makeAutoShuffleList()) {
1591 mPlayListLen = 0;
1592 doAutoShuffleUpdate();
1593 mPlayPos = 0;
1594 openCurrent();
1595 play();
1596 notifyChange(META_CHANGED);
1597 return;
1598 } else {
1599 // failed to build a list of files to shuffle
1600 mShuffleMode = SHUFFLE_NONE;
1601 }
1602 }
1603 saveQueue(false);
1604 }
1605 }
1606 public int getShuffleMode() {
1607 return mShuffleMode;
1608 }
1609
1610 public void setRepeatMode(int repeatmode) {
1611 synchronized(this) {
1612 mRepeatMode = repeatmode;
1613 saveQueue(false);
1614 }
1615 }
1616 public int getRepeatMode() {
1617 return mRepeatMode;
1618 }
1619
1620 public int getMediaMountedCount() {
1621 return mMediaMountedCount;
1622 }
1623
1624 /**
1625 * Returns the path of the currently playing file, or null if
1626 * no file is currently playing.
1627 */
1628 public String getPath() {
1629 return mFileToPlay;
1630 }
1631
1632 /**
1633 * Returns the rowid of the currently playing file, or -1 if
1634 * no file is currently playing.
1635 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001636 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001637 synchronized (this) {
1638 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1639 return mPlayList[mPlayPos];
1640 }
1641 }
1642 return -1;
1643 }
1644
1645 /**
1646 * Returns the position in the queue
1647 * @return the position in the queue
1648 */
1649 public int getQueuePosition() {
1650 synchronized(this) {
1651 return mPlayPos;
1652 }
1653 }
1654
1655 /**
1656 * Starts playing the track at the given position in the queue.
1657 * @param pos The position in the queue of the track that will be played.
1658 */
1659 public void setQueuePosition(int pos) {
1660 synchronized(this) {
1661 stop(false);
1662 mPlayPos = pos;
1663 openCurrent();
1664 play();
1665 notifyChange(META_CHANGED);
Marco Nelissenec0c57a2009-12-12 12:27:11 -08001666 if (mShuffleMode == SHUFFLE_AUTO) {
1667 doAutoShuffleUpdate();
1668 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001669 }
1670 }
1671
1672 public String getArtistName() {
1673 synchronized(this) {
1674 if (mCursor == null) {
1675 return null;
1676 }
1677 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1678 }
1679 }
1680
Marco Nelissenbd447b62009-06-29 14:52:05 -07001681 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001682 synchronized (this) {
1683 if (mCursor == null) {
1684 return -1;
1685 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001686 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001687 }
1688 }
1689
1690 public String getAlbumName() {
1691 synchronized (this) {
1692 if (mCursor == null) {
1693 return null;
1694 }
1695 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1696 }
1697 }
1698
Marco Nelissenbd447b62009-06-29 14:52:05 -07001699 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001700 synchronized (this) {
1701 if (mCursor == null) {
1702 return -1;
1703 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001704 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001705 }
1706 }
1707
1708 public String getTrackName() {
1709 synchronized (this) {
1710 if (mCursor == null) {
1711 return null;
1712 }
1713 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1714 }
1715 }
1716
1717 private boolean isPodcast() {
1718 synchronized (this) {
1719 if (mCursor == null) {
1720 return false;
1721 }
1722 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1723 }
1724 }
1725
1726 private long getBookmark() {
1727 synchronized (this) {
1728 if (mCursor == null) {
1729 return 0;
1730 }
1731 return mCursor.getLong(BOOKMARKCOLIDX);
1732 }
1733 }
1734
1735 /**
1736 * Returns the duration of the file in milliseconds.
1737 * Currently this method returns -1 for the duration of MIDI files.
1738 */
1739 public long duration() {
1740 if (mPlayer.isInitialized()) {
1741 return mPlayer.duration();
1742 }
1743 return -1;
1744 }
1745
1746 /**
1747 * Returns the current playback position in milliseconds
1748 */
1749 public long position() {
1750 if (mPlayer.isInitialized()) {
1751 return mPlayer.position();
1752 }
1753 return -1;
1754 }
1755
1756 /**
1757 * Seeks to the position specified.
1758 *
1759 * @param pos The position to seek to, in milliseconds
1760 */
1761 public long seek(long pos) {
1762 if (mPlayer.isInitialized()) {
1763 if (pos < 0) pos = 0;
1764 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1765 return mPlayer.seek(pos);
1766 }
1767 return -1;
1768 }
1769
1770 /**
Eric Laurent1cc72a12010-06-28 11:27:01 -07001771 * Sets the audio session ID.
1772 *
1773 * @param sessionId: the audio session ID.
1774 */
1775 public void setAudioSessionId(int sessionId) {
1776 synchronized (this) {
1777 mPlayer.setAudioSessionId(sessionId);
1778 }
1779 }
1780
1781 /**
1782 * Returns the audio session ID.
1783 */
1784 public int getAudioSessionId() {
1785 synchronized (this) {
1786 return mPlayer.getAudioSessionId();
1787 }
1788 }
1789
1790 /**
The Android Open Source Project792a2202009-03-03 19:32:30 -08001791 * Provides a unified interface for dealing with midi files and
1792 * other media files.
1793 */
1794 private class MultiPlayer {
1795 private MediaPlayer mMediaPlayer = new MediaPlayer();
1796 private Handler mHandler;
1797 private boolean mIsInitialized = false;
1798
1799 public MultiPlayer() {
1800 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1801 }
1802
The Android Open Source Project792a2202009-03-03 19:32:30 -08001803 public void setDataSource(String path) {
1804 try {
1805 mMediaPlayer.reset();
1806 mMediaPlayer.setOnPreparedListener(null);
1807 if (path.startsWith("content://")) {
1808 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1809 } else {
1810 mMediaPlayer.setDataSource(path);
1811 }
1812 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1813 mMediaPlayer.prepare();
1814 } catch (IOException ex) {
1815 // TODO: notify the user why the file couldn't be opened
1816 mIsInitialized = false;
1817 return;
1818 } catch (IllegalArgumentException ex) {
1819 // TODO: notify the user why the file couldn't be opened
1820 mIsInitialized = false;
1821 return;
1822 }
1823 mMediaPlayer.setOnCompletionListener(listener);
1824 mMediaPlayer.setOnErrorListener(errorListener);
Marco Nelissenf2ef3b52010-09-21 15:47:27 -07001825 Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
1826 i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
1827 i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
1828 sendBroadcast(i);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001829 mIsInitialized = true;
1830 }
1831
1832 public boolean isInitialized() {
1833 return mIsInitialized;
1834 }
1835
1836 public void start() {
Marco Nelissen39888902010-03-02 10:27:05 -08001837 MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001838 mMediaPlayer.start();
1839 }
1840
1841 public void stop() {
1842 mMediaPlayer.reset();
1843 mIsInitialized = false;
1844 }
1845
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001846 /**
1847 * You CANNOT use this player anymore after calling release()
1848 */
1849 public void release() {
1850 stop();
1851 mMediaPlayer.release();
1852 }
1853
The Android Open Source Project792a2202009-03-03 19:32:30 -08001854 public void pause() {
1855 mMediaPlayer.pause();
1856 }
1857
The Android Open Source Project792a2202009-03-03 19:32:30 -08001858 public void setHandler(Handler handler) {
1859 mHandler = handler;
1860 }
1861
1862 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1863 public void onCompletion(MediaPlayer mp) {
1864 // Acquire a temporary wakelock, since when we return from
1865 // this callback the MediaPlayer will release its wakelock
1866 // and allow the device to go to sleep.
1867 // This temporary wakelock is released when the RELEASE_WAKELOCK
1868 // message is processed, but just in case, put a timeout on it.
1869 mWakeLock.acquire(30000);
1870 mHandler.sendEmptyMessage(TRACK_ENDED);
1871 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1872 }
1873 };
1874
The Android Open Source Project792a2202009-03-03 19:32:30 -08001875 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1876 public boolean onError(MediaPlayer mp, int what, int extra) {
1877 switch (what) {
1878 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1879 mIsInitialized = false;
1880 mMediaPlayer.release();
1881 // Creating a new MediaPlayer and settings its wakemode does not
1882 // require the media service, so it's OK to do this now, while the
1883 // service is still being restarted
1884 mMediaPlayer = new MediaPlayer();
1885 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1886 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1887 return true;
1888 default:
Marco Nelissene99341f2009-11-11 11:13:51 -08001889 Log.d("MultiPlayer", "Error: " + what + "," + extra);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001890 break;
1891 }
1892 return false;
1893 }
1894 };
1895
1896 public long duration() {
1897 return mMediaPlayer.getDuration();
1898 }
1899
1900 public long position() {
1901 return mMediaPlayer.getCurrentPosition();
1902 }
1903
1904 public long seek(long whereto) {
1905 mMediaPlayer.seekTo((int) whereto);
1906 return whereto;
1907 }
1908
1909 public void setVolume(float vol) {
1910 mMediaPlayer.setVolume(vol, vol);
1911 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07001912
1913 public void setAudioSessionId(int sessionId) {
1914 mMediaPlayer.setAudioSessionId(sessionId);
1915 }
1916
1917 public int getAudioSessionId() {
1918 return mMediaPlayer.getAudioSessionId();
1919 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001920 }
1921
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001922 /*
1923 * By making this a static class with a WeakReference to the Service, we
1924 * ensure that the Service can be GCd even when the system process still
1925 * has a remote reference to the stub.
1926 */
1927 static class ServiceStub extends IMediaPlaybackService.Stub {
1928 WeakReference<MediaPlaybackService> mService;
1929
1930 ServiceStub(MediaPlaybackService service) {
1931 mService = new WeakReference<MediaPlaybackService>(service);
1932 }
1933
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001934 public void openFile(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001935 {
Marco Nelissen8d08ec22010-05-10 14:05:24 -07001936 mService.get().open(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001937 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001938 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001939 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001940 }
1941 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001942 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001943 }
1944 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001945 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001946 }
1947 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001948 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001949 }
1950 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001951 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001952 }
1953 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001954 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001955 }
1956 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001957 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001958 }
1959 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001960 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001961 }
1962 public void next() {
Marco Nelissenbd0d2f12012-02-27 16:02:15 -08001963 mService.get().gotoNext(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001964 }
1965 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001966 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001967 }
1968 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001969 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001970 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001971 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001972 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001973 }
1974 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001975 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001976 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001977 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001978 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001979 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001980 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001981 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001982 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001983 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001984 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001985 }
1986 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001987 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001988 }
1989 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001990 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001991 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001992 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001993 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001994 }
1995 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001996 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001997 }
1998 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001999 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002000 }
2001 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002002 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002003 }
2004 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002005 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002006 }
2007 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002008 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002009 }
2010 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002011 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002012 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07002013 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002014 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002015 }
2016 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002017 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002018 }
2019 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002020 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002021 }
2022 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002023 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08002024 }
Eric Laurent1cc72a12010-06-28 11:27:01 -07002025 public int getAudioSessionId() {
2026 return mService.get().getAudioSessionId();
2027 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002028 }
Marco Nelissen39888902010-03-02 10:27:05 -08002029
2030 @Override
2031 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002032 writer.println("" + mPlayListLen + " items in queue, currently at index " + mPlayPos);
Marco Nelissen39888902010-03-02 10:27:05 -08002033 writer.println("Currently loaded:");
2034 writer.println(getArtistName());
2035 writer.println(getAlbumName());
2036 writer.println(getTrackName());
2037 writer.println(getPath());
2038 writer.println("playing: " + mIsSupposedToBePlaying);
2039 writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
Marco Nelissenbf555ce2010-03-02 16:55:25 -08002040 writer.println("shuffle mode: " + mShuffleMode);
Marco Nelissen39888902010-03-02 10:27:05 -08002041 MusicUtils.debugDump(writer);
2042 }
2043
Marco Nelissen2b0b9132009-05-21 16:26:47 -07002044 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08002045}