blob: f92bc6d914ea48cd2be89bcefac6d08b8c139123 [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;
The Android Open Source Project792a2202009-03-03 19:32:30 -080023import android.content.ContentResolver;
24import android.content.ContentUris;
25import android.content.ContentValues;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.BroadcastReceiver;
30import android.content.SharedPreferences;
31import android.content.SharedPreferences.Editor;
32import android.database.Cursor;
33import android.database.sqlite.SQLiteException;
The Android Open Source Project792a2202009-03-03 19:32:30 -080034import android.media.AudioManager;
35import android.media.MediaFile;
36import android.media.MediaPlayer;
37import android.net.Uri;
38import android.os.Environment;
39import android.os.FileUtils;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.Message;
43import android.os.PowerManager;
44import android.os.SystemClock;
45import android.os.PowerManager.WakeLock;
46import android.provider.MediaStore;
Marco Nelissen3d54a512009-06-03 16:09:18 -070047import android.telephony.PhoneStateListener;
48import android.telephony.TelephonyManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080049import android.util.Log;
50import android.widget.RemoteViews;
51import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080052
53import java.io.IOException;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070054import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080055import java.util.Random;
56import java.util.Vector;
57
58/**
59 * Provides "background" audio playback capabilities, allowing the
60 * user to switch between activities without stopping playback.
61 */
62public class MediaPlaybackService extends Service {
63 /** used to specify whether enqueue() should start playing
64 * the new list of files right away, next or once all the currently
65 * queued files have been played
66 */
67 public static final int NOW = 1;
68 public static final int NEXT = 2;
69 public static final int LAST = 3;
70 public static final int PLAYBACKSERVICE_STATUS = 1;
71
72 public static final int SHUFFLE_NONE = 0;
73 public static final int SHUFFLE_NORMAL = 1;
74 public static final int SHUFFLE_AUTO = 2;
75
76 public static final int REPEAT_NONE = 0;
77 public static final int REPEAT_CURRENT = 1;
78 public static final int REPEAT_ALL = 2;
79
80 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
81 public static final String META_CHANGED = "com.android.music.metachanged";
82 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
83 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
84 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
85
86 public static final String SERVICECMD = "com.android.music.musicservicecommand";
87 public static final String CMDNAME = "command";
88 public static final String CMDTOGGLEPAUSE = "togglepause";
89 public static final String CMDSTOP = "stop";
90 public static final String CMDPAUSE = "pause";
91 public static final String CMDPREVIOUS = "previous";
92 public static final String CMDNEXT = "next";
93
94 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
95 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
96 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
97 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
98
The Android Open Source Project792a2202009-03-03 19:32:30 -080099 private static final int TRACK_ENDED = 1;
100 private static final int RELEASE_WAKELOCK = 2;
101 private static final int SERVER_DIED = 3;
102 private static final int FADEIN = 4;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700103 private static final int MAX_HISTORY_SIZE = 100;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800104
105 private MultiPlayer mPlayer;
106 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800107 private int mShuffleMode = SHUFFLE_NONE;
108 private int mRepeatMode = REPEAT_NONE;
109 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700110 private long [] mAutoShuffleList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800111 private boolean mOneShot;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700112 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800113 private int mPlayListLen = 0;
114 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
115 private Cursor mCursor;
116 private int mPlayPos = -1;
117 private static final String LOGTAG = "MediaPlaybackService";
118 private final Shuffler mRand = new Shuffler();
119 private int mOpenFailedCounter = 0;
120 String[] mCursorCols = new String[] {
121 "audio._id AS _id", // index must match IDCOLIDX below
122 MediaStore.Audio.Media.ARTIST,
123 MediaStore.Audio.Media.ALBUM,
124 MediaStore.Audio.Media.TITLE,
125 MediaStore.Audio.Media.DATA,
126 MediaStore.Audio.Media.MIME_TYPE,
127 MediaStore.Audio.Media.ALBUM_ID,
128 MediaStore.Audio.Media.ARTIST_ID,
129 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
130 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
131 };
132 private final static int IDCOLIDX = 0;
133 private final static int PODCASTCOLIDX = 8;
134 private final static int BOOKMARKCOLIDX = 9;
135 private BroadcastReceiver mUnmountReceiver = null;
136 private WakeLock mWakeLock;
137 private int mServiceStartId = -1;
138 private boolean mServiceInUse = false;
139 private boolean mResumeAfterCall = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700140 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800141 private boolean mQuietMode = false;
142
143 private SharedPreferences mPreferences;
144 // We use this to distinguish between different cards when saving/restoring playlists.
145 // This will have to change if we want to support multiple simultaneous cards.
146 private int mCardId;
147
The Android Open Source Project490384b2009-03-11 12:11:59 -0700148 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800149
150 // interval after which we stop the service when idle
151 private static final int IDLE_DELAY = 60000;
152
Marco Nelissen3d54a512009-06-03 16:09:18 -0700153 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800154 @Override
Marco Nelissen3d54a512009-06-03 16:09:18 -0700155 public void onCallStateChanged(int state, String incomingNumber) {
156 if (state == TelephonyManager.CALL_STATE_RINGING) {
157 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
158 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
159 if (ringvolume > 0) {
160 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
161 pause();
162 }
163 } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
164 // pause the music while a conversation is in progress
165 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
166 pause();
167 } else if (state == TelephonyManager.CALL_STATE_IDLE) {
168 // start playing again
169 if (mResumeAfterCall) {
170 // resume playback only if music was playing
171 // when the call was answered
172 startAndFadeIn();
173 mResumeAfterCall = false;
174 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800175 }
176 }
177 };
178
179 private void startAndFadeIn() {
180 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
181 }
182
183 private Handler mMediaplayerHandler = new Handler() {
184 float mCurrentVolume = 1.0f;
185 @Override
186 public void handleMessage(Message msg) {
187 switch (msg.what) {
188 case FADEIN:
189 if (!isPlaying()) {
190 mCurrentVolume = 0f;
191 mPlayer.setVolume(mCurrentVolume);
192 play();
193 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
194 } else {
195 mCurrentVolume += 0.01f;
196 if (mCurrentVolume < 1.0f) {
197 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
198 } else {
199 mCurrentVolume = 1.0f;
200 }
201 mPlayer.setVolume(mCurrentVolume);
202 }
203 break;
204 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700205 if (mIsSupposedToBePlaying) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800206 next(true);
207 } else {
208 // the server died when we were idle, so just
209 // reopen the same song (it will start again
210 // from the beginning though when the user
211 // restarts)
212 openCurrent();
213 }
214 break;
215 case TRACK_ENDED:
216 if (mRepeatMode == REPEAT_CURRENT) {
217 seek(0);
218 play();
219 } else if (!mOneShot) {
220 next(false);
221 } else {
222 notifyChange(PLAYBACK_COMPLETE);
Marco Nelissenc1333372009-05-06 12:54:47 -0700223 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800224 }
225 break;
226 case RELEASE_WAKELOCK:
227 mWakeLock.release();
228 break;
229 default:
230 break;
231 }
232 }
233 };
234
235 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
236 @Override
237 public void onReceive(Context context, Intent intent) {
238 String action = intent.getAction();
239 String cmd = intent.getStringExtra("command");
240 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
241 next(true);
242 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
243 prev();
244 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
245 if (isPlaying()) {
246 pause();
247 } else {
248 play();
249 }
250 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
251 pause();
252 } else if (CMDSTOP.equals(cmd)) {
253 pause();
254 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700255 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
256 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800257 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700258 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
259 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800260 }
261 }
262 };
263
264 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800265 }
266
267 @Override
268 public void onCreate() {
269 super.onCreate();
270
271 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
272 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
273
274 registerExternalStorageListener();
275
276 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
277 mPlayer = new MultiPlayer();
278 mPlayer.setHandler(mMediaplayerHandler);
279
The Android Open Source Project792a2202009-03-03 19:32:30 -0800280 reloadQueue();
281
282 IntentFilter commandFilter = new IntentFilter();
283 commandFilter.addAction(SERVICECMD);
284 commandFilter.addAction(TOGGLEPAUSE_ACTION);
285 commandFilter.addAction(PAUSE_ACTION);
286 commandFilter.addAction(NEXT_ACTION);
287 commandFilter.addAction(PREVIOUS_ACTION);
288 registerReceiver(mIntentReceiver, commandFilter);
289
Marco Nelissen3d54a512009-06-03 16:09:18 -0700290 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
291 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800292 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
293 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
294 mWakeLock.setReferenceCounted(false);
295
296 // If the service was idle, but got killed before it stopped itself, the
297 // system will relaunch it. Make sure it gets stopped again in that case.
298 Message msg = mDelayedStopHandler.obtainMessage();
299 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
300 }
301
302 @Override
303 public void onDestroy() {
304 // Check that we're not being destroyed while something is still playing.
305 if (isPlaying()) {
306 Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
307 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700308 // release all MediaPlayer resources, including the native player and wakelocks
309 mPlayer.release();
310 mPlayer = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800311
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700312 // make sure there aren't any other messages coming
313 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700314 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700315
The Android Open Source Project792a2202009-03-03 19:32:30 -0800316 if (mCursor != null) {
317 mCursor.close();
318 mCursor = null;
319 }
320
Marco Nelissen3d54a512009-06-03 16:09:18 -0700321 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
322 tmgr.listen(mPhoneStateListener, 0);
323
The Android Open Source Project792a2202009-03-03 19:32:30 -0800324 unregisterReceiver(mIntentReceiver);
325 if (mUnmountReceiver != null) {
326 unregisterReceiver(mUnmountReceiver);
327 mUnmountReceiver = null;
328 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800329 mWakeLock.release();
330 super.onDestroy();
331 }
332
333 private final char hexdigits [] = new char [] {
334 '0', '1', '2', '3',
335 '4', '5', '6', '7',
336 '8', '9', 'a', 'b',
337 'c', 'd', 'e', 'f'
338 };
339
340 private void saveQueue(boolean full) {
341 if (mOneShot) {
342 return;
343 }
344 Editor ed = mPreferences.edit();
345 //long start = System.currentTimeMillis();
346 if (full) {
347 StringBuilder q = new StringBuilder();
348
349 // The current playlist is saved as a list of "reverse hexadecimal"
350 // numbers, which we can generate faster than normal decimal or
351 // hexadecimal numbers, which in turn allows us to save the playlist
352 // more often without worrying too much about performance.
353 // (saving the full state takes about 40 ms under no-load conditions
354 // on the phone)
355 int len = mPlayListLen;
356 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700357 long n = mPlayList[i];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800358 if (n == 0) {
359 q.append("0;");
360 } else {
361 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700362 int digit = (int)(n & 0xf);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800363 n >>= 4;
364 q.append(hexdigits[digit]);
365 }
366 q.append(";");
367 }
368 }
369 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
370 ed.putString("queue", q.toString());
371 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700372 if (mShuffleMode != SHUFFLE_NONE) {
373 // In shuffle mode we need to save the history too
374 len = mHistory.size();
375 q.setLength(0);
376 for (int i = 0; i < len; i++) {
377 int n = mHistory.get(i);
378 if (n == 0) {
379 q.append("0;");
380 } else {
381 while (n != 0) {
382 int digit = (n & 0xf);
383 n >>= 4;
384 q.append(hexdigits[digit]);
385 }
386 q.append(";");
387 }
388 }
389 ed.putString("history", q.toString());
390 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800391 }
392 ed.putInt("curpos", mPlayPos);
393 if (mPlayer.isInitialized()) {
394 ed.putLong("seekpos", mPlayer.position());
395 }
396 ed.putInt("repeatmode", mRepeatMode);
397 ed.putInt("shufflemode", mShuffleMode);
398 ed.commit();
399
400 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
401 }
402
403 private void reloadQueue() {
404 String q = null;
405
406 boolean newstyle = false;
407 int id = mCardId;
408 if (mPreferences.contains("cardid")) {
409 newstyle = true;
410 id = mPreferences.getInt("cardid", ~mCardId);
411 }
412 if (id == mCardId) {
413 // Only restore the saved playlist if the card is still
414 // the same one as when the playlist was saved
415 q = mPreferences.getString("queue", "");
416 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700417 int qlen = q != null ? q.length() : 0;
418 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800419 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700420 int plen = 0;
421 int n = 0;
422 int shift = 0;
423 for (int i = 0; i < qlen; i++) {
424 char c = q.charAt(i);
425 if (c == ';') {
426 ensurePlayListCapacity(plen + 1);
427 mPlayList[plen] = n;
428 plen++;
429 n = 0;
430 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800431 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700432 if (c >= '0' && c <= '9') {
433 n += ((c - '0') << shift);
434 } else if (c >= 'a' && c <= 'f') {
435 n += ((10 + c - 'a') << shift);
436 } else {
437 // bogus playlist data
438 plen = 0;
439 break;
440 }
441 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800442 }
443 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700444 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800445
446 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700447 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800448 // The saved playlist is bogus, discard it
449 mPlayListLen = 0;
450 return;
451 }
452 mPlayPos = pos;
453
454 // When reloadQueue is called in response to a card-insertion,
455 // we might not be able to query the media provider right away.
456 // To deal with this, try querying for the current file, and if
457 // that fails, wait a while and try again. If that too fails,
458 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700459 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800460 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
461 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700462 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800463 // wait a bit and try again
464 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700465 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800466 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
467 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
468 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700469 if (crsr != null) {
470 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800471 }
472
473 // Make sure we don't auto-skip to the next song, since that
474 // also starts playback. What could happen in that case is:
475 // - music is paused
476 // - go to UMS and delete some files, including the currently playing one
477 // - come back from UMS
478 // (time passes)
479 // - music app is killed for some reason (out of memory)
480 // - music service is restarted, service restores state, doesn't find
481 // the "current" file, goes to the next and: playback starts on its
482 // own, potentially at some random inconvenient time.
483 mOpenFailedCounter = 20;
484 mQuietMode = true;
485 openCurrent();
486 mQuietMode = false;
487 if (!mPlayer.isInitialized()) {
488 // couldn't restore the saved state
489 mPlayListLen = 0;
490 return;
491 }
492
493 long seekpos = mPreferences.getLong("seekpos", 0);
494 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
495
496 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
497 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
498 repmode = REPEAT_NONE;
499 }
500 mRepeatMode = repmode;
501
502 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
503 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
504 shufmode = SHUFFLE_NONE;
505 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700506 if (shufmode != SHUFFLE_NONE) {
507 // in shuffle mode we need to restore the history too
508 q = mPreferences.getString("history", "");
509 qlen = q != null ? q.length() : 0;
510 if (qlen > 1) {
511 plen = 0;
512 n = 0;
513 shift = 0;
514 mHistory.clear();
515 for (int i = 0; i < qlen; i++) {
516 char c = q.charAt(i);
517 if (c == ';') {
518 if (n >= mPlayListLen) {
519 // bogus history data
520 mHistory.clear();
521 break;
522 }
523 mHistory.add(n);
524 n = 0;
525 shift = 0;
526 } else {
527 if (c >= '0' && c <= '9') {
528 n += ((c - '0') << shift);
529 } else if (c >= 'a' && c <= 'f') {
530 n += ((10 + c - 'a') << shift);
531 } else {
532 // bogus history data
533 mHistory.clear();
534 break;
535 }
536 shift += 4;
537 }
538 }
539 }
540 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800541 if (shufmode == SHUFFLE_AUTO) {
542 if (! makeAutoShuffleList()) {
543 shufmode = SHUFFLE_NONE;
544 }
545 }
546 mShuffleMode = shufmode;
547 }
548 }
549
550 @Override
551 public IBinder onBind(Intent intent) {
552 mDelayedStopHandler.removeCallbacksAndMessages(null);
553 mServiceInUse = true;
554 return mBinder;
555 }
556
557 @Override
558 public void onRebind(Intent intent) {
559 mDelayedStopHandler.removeCallbacksAndMessages(null);
560 mServiceInUse = true;
561 }
562
563 @Override
Marco Nelissenc1017e52009-09-24 13:21:06 -0700564 public int onStartCommand(Intent intent, int flags, int startId) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800565 mServiceStartId = startId;
566 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700567
568 if (intent != null) {
569 String action = intent.getAction();
570 String cmd = intent.getStringExtra("command");
571
572 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
573 next(true);
574 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
575 prev();
576 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
577 if (isPlaying()) {
578 pause();
579 } else {
580 play();
581 }
582 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800583 pause();
Marco Nelissenc1017e52009-09-24 13:21:06 -0700584 } else if (CMDSTOP.equals(cmd)) {
585 pause();
586 seek(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800587 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800588 }
589
590 // make sure the service will shut down on its own if it was
591 // just started but not bound to and nothing is playing
592 mDelayedStopHandler.removeCallbacksAndMessages(null);
593 Message msg = mDelayedStopHandler.obtainMessage();
594 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Marco Nelissenc1017e52009-09-24 13:21:06 -0700595 return START_STICKY;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800596 }
597
598 @Override
599 public boolean onUnbind(Intent intent) {
600 mServiceInUse = false;
601
602 // Take a snapshot of the current playlist
603 saveQueue(true);
604
605 if (isPlaying() || mResumeAfterCall) {
606 // something is currently playing, or will be playing once
607 // an in-progress call ends, so don't stop the service now.
608 return true;
609 }
610
611 // If there is a playlist but playback is paused, then wait a while
612 // before stopping the service, so that pause/resume isn't slow.
613 // Also delay stopping the service if we're transitioning between tracks.
614 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
615 Message msg = mDelayedStopHandler.obtainMessage();
616 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
617 return true;
618 }
619
620 // No active playlist, OK to stop the service right now
621 stopSelf(mServiceStartId);
622 return true;
623 }
624
625 private Handler mDelayedStopHandler = new Handler() {
626 @Override
627 public void handleMessage(Message msg) {
628 // Check again to make sure nothing is playing right now
629 if (isPlaying() || mResumeAfterCall || mServiceInUse
630 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
631 return;
632 }
633 // save the queue again, because it might have changed
634 // since the user exited the music app (because of
635 // party-shuffle or because the play-position changed)
636 saveQueue(true);
637 stopSelf(mServiceStartId);
638 }
639 };
640
641 /**
642 * Called when we receive a ACTION_MEDIA_EJECT notification.
643 *
644 * @param storagePath path to mount point for the removed media
645 */
646 public void closeExternalStorageFiles(String storagePath) {
647 // stop playback and clean up if the SD card is going to be unmounted.
648 stop(true);
649 notifyChange(QUEUE_CHANGED);
650 notifyChange(META_CHANGED);
651 }
652
653 /**
654 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
655 * The intent will call closeExternalStorageFiles() if the external media
656 * is going to be ejected, so applications can clean up any files they have open.
657 */
658 public void registerExternalStorageListener() {
659 if (mUnmountReceiver == null) {
660 mUnmountReceiver = new BroadcastReceiver() {
661 @Override
662 public void onReceive(Context context, Intent intent) {
663 String action = intent.getAction();
664 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
665 saveQueue(true);
666 mOneShot = true; // This makes us not save the state again later,
667 // which would be wrong because the song ids and
668 // card id might not match.
669 closeExternalStorageFiles(intent.getData().getPath());
670 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
671 mMediaMountedCount++;
672 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
673 reloadQueue();
674 notifyChange(QUEUE_CHANGED);
675 notifyChange(META_CHANGED);
676 }
677 }
678 };
679 IntentFilter iFilter = new IntentFilter();
680 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
681 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
682 iFilter.addDataScheme("file");
683 registerReceiver(mUnmountReceiver, iFilter);
684 }
685 }
686
687 /**
688 * Notify the change-receivers that something has changed.
689 * The intent that is sent contains the following data
690 * for the currently playing track:
691 * "id" - Integer: the database row ID
692 * "artist" - String: the name of the artist
693 * "album" - String: the name of the album
694 * "track" - String: the name of the track
695 * The intent has an action that is one of
696 * "com.android.music.metachanged"
697 * "com.android.music.queuechanged",
698 * "com.android.music.playbackcomplete"
699 * "com.android.music.playstatechanged"
700 * respectively indicating that a new track has
701 * started playing, that the playback queue has
702 * changed, that playback has stopped because
703 * the last file in the list has been played,
704 * or that the play-state changed (paused/resumed).
705 */
706 private void notifyChange(String what) {
707
708 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700709 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800710 i.putExtra("artist", getArtistName());
711 i.putExtra("album",getAlbumName());
712 i.putExtra("track", getTrackName());
713 sendBroadcast(i);
714
715 if (what.equals(QUEUE_CHANGED)) {
716 saveQueue(true);
717 } else {
718 saveQueue(false);
719 }
720
The Android Open Source Project490384b2009-03-11 12:11:59 -0700721 // Share this notification directly with our widgets
722 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800723 }
724
725 private void ensurePlayListCapacity(int size) {
726 if (mPlayList == null || size > mPlayList.length) {
727 // reallocate at 2x requested size so we don't
728 // need to grow and copy the array for every
729 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700730 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700731 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800732 for (int i = 0; i < len; i++) {
733 newlist[i] = mPlayList[i];
734 }
735 mPlayList = newlist;
736 }
737 // FIXME: shrink the array when the needed size is much smaller
738 // than the allocated size
739 }
740
741 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700742 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800743 int addlen = list.length;
744 if (position < 0) { // overwrite
745 mPlayListLen = 0;
746 position = 0;
747 }
748 ensurePlayListCapacity(mPlayListLen + addlen);
749 if (position > mPlayListLen) {
750 position = mPlayListLen;
751 }
752
753 // move part of list after insertion point
754 int tailsize = mPlayListLen - position;
755 for (int i = tailsize ; i > 0 ; i--) {
756 mPlayList[position + i] = mPlayList[position + i - addlen];
757 }
758
759 // copy list into playlist
760 for (int i = 0; i < addlen; i++) {
761 mPlayList[position + i] = list[i];
762 }
763 mPlayListLen += addlen;
764 }
765
766 /**
767 * Appends a list of tracks to the current playlist.
768 * If nothing is playing currently, playback will be started at
769 * the first track.
770 * If the action is NOW, playback will switch to the first of
771 * the new tracks immediately.
772 * @param list The list of tracks to append.
773 * @param action NOW, NEXT or LAST
774 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700775 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800776 synchronized(this) {
777 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
778 addToPlayList(list, mPlayPos + 1);
779 notifyChange(QUEUE_CHANGED);
780 } else {
781 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
782 addToPlayList(list, Integer.MAX_VALUE);
783 notifyChange(QUEUE_CHANGED);
784 if (action == NOW) {
785 mPlayPos = mPlayListLen - list.length;
786 openCurrent();
787 play();
788 notifyChange(META_CHANGED);
789 return;
790 }
791 }
792 if (mPlayPos < 0) {
793 mPlayPos = 0;
794 openCurrent();
795 play();
796 notifyChange(META_CHANGED);
797 }
798 }
799 }
800
801 /**
802 * Replaces the current playlist with a new list,
803 * and prepares for starting playback at the specified
804 * position in the list, or a random position if the
805 * specified position is 0.
806 * @param list The new list of tracks.
807 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700808 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800809 synchronized (this) {
810 if (mShuffleMode == SHUFFLE_AUTO) {
811 mShuffleMode = SHUFFLE_NORMAL;
812 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700813 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800814 int listlength = list.length;
815 boolean newlist = true;
816 if (mPlayListLen == listlength) {
817 // possible fast path: list might be the same
818 newlist = false;
819 for (int i = 0; i < listlength; i++) {
820 if (list[i] != mPlayList[i]) {
821 newlist = true;
822 break;
823 }
824 }
825 }
826 if (newlist) {
827 addToPlayList(list, -1);
828 notifyChange(QUEUE_CHANGED);
829 }
830 int oldpos = mPlayPos;
831 if (position >= 0) {
832 mPlayPos = position;
833 } else {
834 mPlayPos = mRand.nextInt(mPlayListLen);
835 }
836 mHistory.clear();
837
838 saveBookmarkIfNeeded();
839 openCurrent();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700840 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800841 notifyChange(META_CHANGED);
842 }
843 }
844 }
845
846 /**
847 * Moves the item at index1 to index2.
848 * @param index1
849 * @param index2
850 */
851 public void moveQueueItem(int index1, int index2) {
852 synchronized (this) {
853 if (index1 >= mPlayListLen) {
854 index1 = mPlayListLen - 1;
855 }
856 if (index2 >= mPlayListLen) {
857 index2 = mPlayListLen - 1;
858 }
859 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700860 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800861 for (int i = index1; i < index2; i++) {
862 mPlayList[i] = mPlayList[i+1];
863 }
864 mPlayList[index2] = tmp;
865 if (mPlayPos == index1) {
866 mPlayPos = index2;
867 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
868 mPlayPos--;
869 }
870 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700871 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800872 for (int i = index1; i > index2; i--) {
873 mPlayList[i] = mPlayList[i-1];
874 }
875 mPlayList[index2] = tmp;
876 if (mPlayPos == index1) {
877 mPlayPos = index2;
878 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
879 mPlayPos++;
880 }
881 }
882 notifyChange(QUEUE_CHANGED);
883 }
884 }
885
886 /**
887 * Returns the current play list
888 * @return An array of integers containing the IDs of the tracks in the play list
889 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700890 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800891 synchronized (this) {
892 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700893 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800894 for (int i = 0; i < len; i++) {
895 list[i] = mPlayList[i];
896 }
897 return list;
898 }
899 }
900
901 private void openCurrent() {
902 synchronized (this) {
903 if (mCursor != null) {
904 mCursor.close();
905 mCursor = null;
906 }
907 if (mPlayListLen == 0) {
908 return;
909 }
910 stop(false);
911
912 String id = String.valueOf(mPlayList[mPlayPos]);
913
914 mCursor = getContentResolver().query(
915 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
916 mCursorCols, "_id=" + id , null, null);
917 if (mCursor != null) {
918 mCursor.moveToFirst();
919 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
920 // go to bookmark if needed
921 if (isPodcast()) {
922 long bookmark = getBookmark();
923 // Start playing a little bit before the bookmark,
924 // so it's easier to get back in to the narrative.
925 seek(bookmark - 5000);
926 }
927 }
928 }
929 }
930
931 public void openAsync(String path) {
932 synchronized (this) {
933 if (path == null) {
934 return;
935 }
936
937 mRepeatMode = REPEAT_NONE;
938 ensurePlayListCapacity(1);
939 mPlayListLen = 1;
940 mPlayPos = -1;
941
942 mFileToPlay = path;
943 mCursor = null;
944 mPlayer.setDataSourceAsync(mFileToPlay);
945 mOneShot = true;
946 }
947 }
948
949 /**
950 * Opens the specified file and readies it for playback.
951 *
952 * @param path The full path of the file to be opened.
953 * @param oneshot when set to true, playback will stop after this file completes, instead
954 * of moving on to the next track in the list
955 */
956 public void open(String path, boolean oneshot) {
957 synchronized (this) {
958 if (path == null) {
959 return;
960 }
961
962 if (oneshot) {
963 mRepeatMode = REPEAT_NONE;
964 ensurePlayListCapacity(1);
965 mPlayListLen = 1;
966 mPlayPos = -1;
967 }
968
969 // if mCursor is null, try to associate path with a database cursor
970 if (mCursor == null) {
971
972 ContentResolver resolver = getContentResolver();
973 Uri uri;
974 String where;
975 String selectionArgs[];
976 if (path.startsWith("content://media/")) {
977 uri = Uri.parse(path);
978 where = null;
979 selectionArgs = null;
980 } else {
981 uri = MediaStore.Audio.Media.getContentUriForPath(path);
982 where = MediaStore.Audio.Media.DATA + "=?";
983 selectionArgs = new String[] { path };
984 }
985
986 try {
987 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
988 if (mCursor != null) {
989 if (mCursor.getCount() == 0) {
990 mCursor.close();
991 mCursor = null;
992 } else {
993 mCursor.moveToNext();
994 ensurePlayListCapacity(1);
995 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700996 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800997 mPlayPos = 0;
998 }
999 }
1000 } catch (UnsupportedOperationException ex) {
1001 }
1002 }
1003 mFileToPlay = path;
1004 mPlayer.setDataSource(mFileToPlay);
1005 mOneShot = oneshot;
1006 if (! mPlayer.isInitialized()) {
1007 stop(true);
1008 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1009 // beware: this ends up being recursive because next() calls open() again.
1010 next(false);
1011 }
1012 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1013 // need to make sure we only shows this once
1014 mOpenFailedCounter = 0;
1015 if (!mQuietMode) {
1016 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1017 }
1018 }
1019 } else {
1020 mOpenFailedCounter = 0;
1021 }
1022 }
1023 }
1024
1025 /**
1026 * Starts playback of a previously opened file.
1027 */
1028 public void play() {
1029 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001030 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001031 long duration = mPlayer.duration();
1032 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1033 mPlayer.position() >= duration - 2000) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001034 next(true);
1035 }
1036
The Android Open Source Project792a2202009-03-03 19:32:30 -08001037 mPlayer.start();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001038
The Android Open Source Project792a2202009-03-03 19:32:30 -08001039 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1040 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1041 if (getAudioId() < 0) {
1042 // streaming
1043 views.setTextViewText(R.id.trackname, getPath());
1044 views.setTextViewText(R.id.artistalbum, null);
1045 } else {
1046 String artist = getArtistName();
1047 views.setTextViewText(R.id.trackname, getTrackName());
1048 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
1049 artist = getString(R.string.unknown_artist_name);
1050 }
1051 String album = getAlbumName();
1052 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1053 album = getString(R.string.unknown_album_name);
1054 }
1055
1056 views.setTextViewText(R.id.artistalbum,
1057 getString(R.string.notification_artist_album, artist, album)
1058 );
1059 }
1060
The Android Open Source Project792a2202009-03-03 19:32:30 -08001061 Notification status = new Notification();
1062 status.contentView = views;
1063 status.flags |= Notification.FLAG_ONGOING_EVENT;
1064 status.icon = R.drawable.stat_notify_musicplayer;
1065 status.contentIntent = PendingIntent.getActivity(this, 0,
1066 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001067 startForeground(PLAYBACKSERVICE_STATUS, status);
Marco Nelissenc1333372009-05-06 12:54:47 -07001068 if (!mIsSupposedToBePlaying) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001069 notifyChange(PLAYSTATE_CHANGED);
1070 }
Marco Nelissenc1333372009-05-06 12:54:47 -07001071 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001072 } else if (mPlayListLen <= 0) {
1073 // This is mostly so that if you press 'play' on a bluetooth headset
1074 // without every having played anything before, it will still play
1075 // something.
1076 setShuffleMode(SHUFFLE_AUTO);
1077 }
1078 }
1079
1080 private void stop(boolean remove_status_icon) {
1081 if (mPlayer.isInitialized()) {
1082 mPlayer.stop();
1083 }
1084 mFileToPlay = null;
1085 if (mCursor != null) {
1086 mCursor.close();
1087 mCursor = null;
1088 }
1089 if (remove_status_icon) {
1090 gotoIdleState();
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001091 } else {
1092 stopForeground(false);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001093 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001094 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001095 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001096 }
1097 }
1098
1099 /**
1100 * Stops playback.
1101 */
1102 public void stop() {
1103 stop(true);
1104 }
1105
1106 /**
1107 * Pauses playback (call play() to resume)
1108 */
1109 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001110 synchronized(this) {
1111 if (isPlaying()) {
1112 mPlayer.pause();
1113 gotoIdleState();
Marco Nelissen407cf912009-05-11 09:56:31 -07001114 mIsSupposedToBePlaying = false;
1115 notifyChange(PLAYSTATE_CHANGED);
1116 saveBookmarkIfNeeded();
1117 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001118 }
1119 }
1120
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001121 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001122 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001123 * @return true if something is playing (or will be playing shortly, in case
1124 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001125 */
1126 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001127 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001128 }
1129
1130 /*
1131 Desired behavior for prev/next/shuffle:
1132
1133 - NEXT will move to the next track in the list when not shuffling, and to
1134 a track randomly picked from the not-yet-played tracks when shuffling.
1135 If all tracks have already been played, pick from the full set, but
1136 avoid picking the previously played track if possible.
1137 - when shuffling, PREV will go to the previously played track. Hitting PREV
1138 again will go to the track played before that, etc. When the start of the
1139 history has been reached, PREV is a no-op.
1140 When not shuffling, PREV will go to the sequentially previous track (the
1141 difference with the shuffle-case is mainly that when not shuffling, the
1142 user can back up to tracks that are not in the history).
1143
1144 Example:
1145 When playing an album with 10 tracks from the start, and enabling shuffle
1146 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1147 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1148 When hitting 'prev' 8 times while playing track 7 in this example, the
1149 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1150 a random track will be picked again. If at any time user disables shuffling
1151 the next/previous track will be picked in sequential order again.
1152 */
1153
1154 public void prev() {
1155 synchronized (this) {
1156 if (mOneShot) {
1157 // we were playing a specific file not part of a playlist, so there is no 'previous'
1158 seek(0);
1159 play();
1160 return;
1161 }
1162 if (mShuffleMode == SHUFFLE_NORMAL) {
1163 // go to previously-played track and remove it from the history
1164 int histsize = mHistory.size();
1165 if (histsize == 0) {
1166 // prev is a no-op
1167 return;
1168 }
1169 Integer pos = mHistory.remove(histsize - 1);
1170 mPlayPos = pos.intValue();
1171 } else {
1172 if (mPlayPos > 0) {
1173 mPlayPos--;
1174 } else {
1175 mPlayPos = mPlayListLen - 1;
1176 }
1177 }
1178 saveBookmarkIfNeeded();
1179 stop(false);
1180 openCurrent();
1181 play();
1182 notifyChange(META_CHANGED);
1183 }
1184 }
1185
1186 public void next(boolean force) {
1187 synchronized (this) {
1188 if (mOneShot) {
1189 // we were playing a specific file not part of a playlist, so there is no 'next'
1190 seek(0);
1191 play();
1192 return;
1193 }
1194
Marco Nelissen663fea32009-06-12 13:39:27 -07001195 if (mPlayListLen <= 0) {
1196 return;
1197 }
1198
The Android Open Source Project792a2202009-03-03 19:32:30 -08001199 // Store the current file in the history, but keep the history at a
1200 // reasonable size
1201 if (mPlayPos >= 0) {
1202 mHistory.add(Integer.valueOf(mPlayPos));
1203 }
1204 if (mHistory.size() > MAX_HISTORY_SIZE) {
1205 mHistory.removeElementAt(0);
1206 }
1207
1208 if (mShuffleMode == SHUFFLE_NORMAL) {
1209 // Pick random next track from the not-yet-played ones
1210 // TODO: make it work right after adding/removing items in the queue.
1211
1212 int numTracks = mPlayListLen;
1213 int[] tracks = new int[numTracks];
1214 for (int i=0;i < numTracks; i++) {
1215 tracks[i] = i;
1216 }
1217
1218 int numHistory = mHistory.size();
1219 int numUnplayed = numTracks;
1220 for (int i=0;i < numHistory; i++) {
1221 int idx = mHistory.get(i).intValue();
1222 if (idx < numTracks && tracks[idx] >= 0) {
1223 numUnplayed--;
1224 tracks[idx] = -1;
1225 }
1226 }
1227
1228 // 'numUnplayed' now indicates how many tracks have not yet
1229 // been played, and 'tracks' contains the indices of those
1230 // tracks.
1231 if (numUnplayed <=0) {
1232 // everything's already been played
1233 if (mRepeatMode == REPEAT_ALL || force) {
1234 //pick from full set
1235 numUnplayed = numTracks;
1236 for (int i=0;i < numTracks; i++) {
1237 tracks[i] = i;
1238 }
1239 } else {
1240 // all done
1241 gotoIdleState();
1242 return;
1243 }
1244 }
1245 int skip = mRand.nextInt(numUnplayed);
1246 int cnt = -1;
1247 while (true) {
1248 while (tracks[++cnt] < 0)
1249 ;
1250 skip--;
1251 if (skip < 0) {
1252 break;
1253 }
1254 }
1255 mPlayPos = cnt;
1256 } else if (mShuffleMode == SHUFFLE_AUTO) {
1257 doAutoShuffleUpdate();
1258 mPlayPos++;
1259 } else {
1260 if (mPlayPos >= mPlayListLen - 1) {
1261 // we're at the end of the list
1262 if (mRepeatMode == REPEAT_NONE && !force) {
1263 // all done
1264 gotoIdleState();
1265 notifyChange(PLAYBACK_COMPLETE);
Marco Nelissenc1333372009-05-06 12:54:47 -07001266 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001267 return;
1268 } else if (mRepeatMode == REPEAT_ALL || force) {
1269 mPlayPos = 0;
1270 }
1271 } else {
1272 mPlayPos++;
1273 }
1274 }
1275 saveBookmarkIfNeeded();
1276 stop(false);
1277 openCurrent();
1278 play();
1279 notifyChange(META_CHANGED);
1280 }
1281 }
1282
1283 private void gotoIdleState() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001284 mDelayedStopHandler.removeCallbacksAndMessages(null);
1285 Message msg = mDelayedStopHandler.obtainMessage();
1286 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
Dianne Hackbornd5fc5b62009-08-18 11:35:30 -07001287 stopForeground(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001288 }
1289
1290 private void saveBookmarkIfNeeded() {
1291 try {
1292 if (isPodcast()) {
1293 long pos = position();
1294 long bookmark = getBookmark();
1295 long duration = duration();
1296 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1297 (pos > bookmark && (pos - 10000) < bookmark)) {
1298 // The existing bookmark is close to the current
1299 // position, so don't update it.
1300 return;
1301 }
1302 if (pos < 15000 || (pos + 10000) > duration) {
1303 // if we're near the start or end, clear the bookmark
1304 pos = 0;
1305 }
1306
1307 // write 'pos' to the bookmark field
1308 ContentValues values = new ContentValues();
1309 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1310 Uri uri = ContentUris.withAppendedId(
1311 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1312 getContentResolver().update(uri, values, null, null);
1313 }
1314 } catch (SQLiteException ex) {
1315 }
1316 }
1317
1318 // Make sure there are at least 5 items after the currently playing item
1319 // and no more than 10 items before.
1320 private void doAutoShuffleUpdate() {
1321 boolean notify = false;
1322 // remove old entries
1323 if (mPlayPos > 10) {
1324 removeTracks(0, mPlayPos - 9);
1325 notify = true;
1326 }
1327 // add new entries if needed
1328 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1329 for (int i = 0; i < to_add; i++) {
1330 // pick something at random from the list
1331 int idx = mRand.nextInt(mAutoShuffleList.length);
Marco Nelissenbd447b62009-06-29 14:52:05 -07001332 long which = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001333 ensurePlayListCapacity(mPlayListLen + 1);
1334 mPlayList[mPlayListLen++] = which;
1335 notify = true;
1336 }
1337 if (notify) {
1338 notifyChange(QUEUE_CHANGED);
1339 }
1340 }
1341
1342 // A simple variation of Random that makes sure that the
1343 // value it returns is not equal to the value it returned
1344 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001345 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001346 private int mPrevious;
1347 private Random mRandom = new Random();
1348 public int nextInt(int interval) {
1349 int ret;
1350 do {
1351 ret = mRandom.nextInt(interval);
1352 } while (ret == mPrevious && interval > 1);
1353 mPrevious = ret;
1354 return ret;
1355 }
1356 };
1357
1358 private boolean makeAutoShuffleList() {
1359 ContentResolver res = getContentResolver();
1360 Cursor c = null;
1361 try {
1362 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1363 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1364 null, null);
1365 if (c == null || c.getCount() == 0) {
1366 return false;
1367 }
1368 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001369 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001370 for (int i = 0; i < len; i++) {
1371 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001372 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001373 }
1374 mAutoShuffleList = list;
1375 return true;
1376 } catch (RuntimeException ex) {
1377 } finally {
1378 if (c != null) {
1379 c.close();
1380 }
1381 }
1382 return false;
1383 }
1384
1385 /**
1386 * Removes the range of tracks specified from the play list. If a file within the range is
1387 * the file currently being played, playback will move to the next file after the
1388 * range.
1389 * @param first The first file to be removed
1390 * @param last The last file to be removed
1391 * @return the number of tracks deleted
1392 */
1393 public int removeTracks(int first, int last) {
1394 int numremoved = removeTracksInternal(first, last);
1395 if (numremoved > 0) {
1396 notifyChange(QUEUE_CHANGED);
1397 }
1398 return numremoved;
1399 }
1400
1401 private int removeTracksInternal(int first, int last) {
1402 synchronized (this) {
1403 if (last < first) return 0;
1404 if (first < 0) first = 0;
1405 if (last >= mPlayListLen) last = mPlayListLen - 1;
1406
1407 boolean gotonext = false;
1408 if (first <= mPlayPos && mPlayPos <= last) {
1409 mPlayPos = first;
1410 gotonext = true;
1411 } else if (mPlayPos > last) {
1412 mPlayPos -= (last - first + 1);
1413 }
1414 int num = mPlayListLen - last - 1;
1415 for (int i = 0; i < num; i++) {
1416 mPlayList[first + i] = mPlayList[last + 1 + i];
1417 }
1418 mPlayListLen -= last - first + 1;
1419
1420 if (gotonext) {
1421 if (mPlayListLen == 0) {
1422 stop(true);
1423 mPlayPos = -1;
1424 } else {
1425 if (mPlayPos >= mPlayListLen) {
1426 mPlayPos = 0;
1427 }
1428 boolean wasPlaying = isPlaying();
1429 stop(false);
1430 openCurrent();
1431 if (wasPlaying) {
1432 play();
1433 }
1434 }
1435 }
1436 return last - first + 1;
1437 }
1438 }
1439
1440 /**
1441 * Removes all instances of the track with the given id
1442 * from the playlist.
1443 * @param id The id to be removed
1444 * @return how many instances of the track were removed
1445 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001446 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001447 int numremoved = 0;
1448 synchronized (this) {
1449 for (int i = 0; i < mPlayListLen; i++) {
1450 if (mPlayList[i] == id) {
1451 numremoved += removeTracksInternal(i, i);
1452 i--;
1453 }
1454 }
1455 }
1456 if (numremoved > 0) {
1457 notifyChange(QUEUE_CHANGED);
1458 }
1459 return numremoved;
1460 }
1461
1462 public void setShuffleMode(int shufflemode) {
1463 synchronized(this) {
1464 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1465 return;
1466 }
1467 mShuffleMode = shufflemode;
1468 if (mShuffleMode == SHUFFLE_AUTO) {
1469 if (makeAutoShuffleList()) {
1470 mPlayListLen = 0;
1471 doAutoShuffleUpdate();
1472 mPlayPos = 0;
1473 openCurrent();
1474 play();
1475 notifyChange(META_CHANGED);
1476 return;
1477 } else {
1478 // failed to build a list of files to shuffle
1479 mShuffleMode = SHUFFLE_NONE;
1480 }
1481 }
1482 saveQueue(false);
1483 }
1484 }
1485 public int getShuffleMode() {
1486 return mShuffleMode;
1487 }
1488
1489 public void setRepeatMode(int repeatmode) {
1490 synchronized(this) {
1491 mRepeatMode = repeatmode;
1492 saveQueue(false);
1493 }
1494 }
1495 public int getRepeatMode() {
1496 return mRepeatMode;
1497 }
1498
1499 public int getMediaMountedCount() {
1500 return mMediaMountedCount;
1501 }
1502
1503 /**
1504 * Returns the path of the currently playing file, or null if
1505 * no file is currently playing.
1506 */
1507 public String getPath() {
1508 return mFileToPlay;
1509 }
1510
1511 /**
1512 * Returns the rowid of the currently playing file, or -1 if
1513 * no file is currently playing.
1514 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001515 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001516 synchronized (this) {
1517 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1518 return mPlayList[mPlayPos];
1519 }
1520 }
1521 return -1;
1522 }
1523
1524 /**
1525 * Returns the position in the queue
1526 * @return the position in the queue
1527 */
1528 public int getQueuePosition() {
1529 synchronized(this) {
1530 return mPlayPos;
1531 }
1532 }
1533
1534 /**
1535 * Starts playing the track at the given position in the queue.
1536 * @param pos The position in the queue of the track that will be played.
1537 */
1538 public void setQueuePosition(int pos) {
1539 synchronized(this) {
1540 stop(false);
1541 mPlayPos = pos;
1542 openCurrent();
1543 play();
1544 notifyChange(META_CHANGED);
1545 }
1546 }
1547
1548 public String getArtistName() {
1549 synchronized(this) {
1550 if (mCursor == null) {
1551 return null;
1552 }
1553 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1554 }
1555 }
1556
Marco Nelissenbd447b62009-06-29 14:52:05 -07001557 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001558 synchronized (this) {
1559 if (mCursor == null) {
1560 return -1;
1561 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001562 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001563 }
1564 }
1565
1566 public String getAlbumName() {
1567 synchronized (this) {
1568 if (mCursor == null) {
1569 return null;
1570 }
1571 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1572 }
1573 }
1574
Marco Nelissenbd447b62009-06-29 14:52:05 -07001575 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001576 synchronized (this) {
1577 if (mCursor == null) {
1578 return -1;
1579 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001580 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001581 }
1582 }
1583
1584 public String getTrackName() {
1585 synchronized (this) {
1586 if (mCursor == null) {
1587 return null;
1588 }
1589 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1590 }
1591 }
1592
1593 private boolean isPodcast() {
1594 synchronized (this) {
1595 if (mCursor == null) {
1596 return false;
1597 }
1598 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1599 }
1600 }
1601
1602 private long getBookmark() {
1603 synchronized (this) {
1604 if (mCursor == null) {
1605 return 0;
1606 }
1607 return mCursor.getLong(BOOKMARKCOLIDX);
1608 }
1609 }
1610
1611 /**
1612 * Returns the duration of the file in milliseconds.
1613 * Currently this method returns -1 for the duration of MIDI files.
1614 */
1615 public long duration() {
1616 if (mPlayer.isInitialized()) {
1617 return mPlayer.duration();
1618 }
1619 return -1;
1620 }
1621
1622 /**
1623 * Returns the current playback position in milliseconds
1624 */
1625 public long position() {
1626 if (mPlayer.isInitialized()) {
1627 return mPlayer.position();
1628 }
1629 return -1;
1630 }
1631
1632 /**
1633 * Seeks to the position specified.
1634 *
1635 * @param pos The position to seek to, in milliseconds
1636 */
1637 public long seek(long pos) {
1638 if (mPlayer.isInitialized()) {
1639 if (pos < 0) pos = 0;
1640 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1641 return mPlayer.seek(pos);
1642 }
1643 return -1;
1644 }
1645
1646 /**
1647 * Provides a unified interface for dealing with midi files and
1648 * other media files.
1649 */
1650 private class MultiPlayer {
1651 private MediaPlayer mMediaPlayer = new MediaPlayer();
1652 private Handler mHandler;
1653 private boolean mIsInitialized = false;
1654
1655 public MultiPlayer() {
1656 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1657 }
1658
1659 public void setDataSourceAsync(String path) {
1660 try {
1661 mMediaPlayer.reset();
1662 mMediaPlayer.setDataSource(path);
1663 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1664 mMediaPlayer.setOnPreparedListener(preparedlistener);
1665 mMediaPlayer.prepareAsync();
1666 } catch (IOException ex) {
1667 // TODO: notify the user why the file couldn't be opened
1668 mIsInitialized = false;
1669 return;
1670 } catch (IllegalArgumentException ex) {
1671 // TODO: notify the user why the file couldn't be opened
1672 mIsInitialized = false;
1673 return;
1674 }
1675 mMediaPlayer.setOnCompletionListener(listener);
1676 mMediaPlayer.setOnErrorListener(errorListener);
1677
1678 mIsInitialized = true;
1679 }
1680
1681 public void setDataSource(String path) {
1682 try {
1683 mMediaPlayer.reset();
1684 mMediaPlayer.setOnPreparedListener(null);
1685 if (path.startsWith("content://")) {
1686 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1687 } else {
1688 mMediaPlayer.setDataSource(path);
1689 }
1690 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1691 mMediaPlayer.prepare();
1692 } catch (IOException ex) {
1693 // TODO: notify the user why the file couldn't be opened
1694 mIsInitialized = false;
1695 return;
1696 } catch (IllegalArgumentException ex) {
1697 // TODO: notify the user why the file couldn't be opened
1698 mIsInitialized = false;
1699 return;
1700 }
1701 mMediaPlayer.setOnCompletionListener(listener);
1702 mMediaPlayer.setOnErrorListener(errorListener);
1703
1704 mIsInitialized = true;
1705 }
1706
1707 public boolean isInitialized() {
1708 return mIsInitialized;
1709 }
1710
1711 public void start() {
1712 mMediaPlayer.start();
1713 }
1714
1715 public void stop() {
1716 mMediaPlayer.reset();
1717 mIsInitialized = false;
1718 }
1719
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001720 /**
1721 * You CANNOT use this player anymore after calling release()
1722 */
1723 public void release() {
1724 stop();
1725 mMediaPlayer.release();
1726 }
1727
The Android Open Source Project792a2202009-03-03 19:32:30 -08001728 public void pause() {
1729 mMediaPlayer.pause();
1730 }
1731
The Android Open Source Project792a2202009-03-03 19:32:30 -08001732 public void setHandler(Handler handler) {
1733 mHandler = handler;
1734 }
1735
1736 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1737 public void onCompletion(MediaPlayer mp) {
1738 // Acquire a temporary wakelock, since when we return from
1739 // this callback the MediaPlayer will release its wakelock
1740 // and allow the device to go to sleep.
1741 // This temporary wakelock is released when the RELEASE_WAKELOCK
1742 // message is processed, but just in case, put a timeout on it.
1743 mWakeLock.acquire(30000);
1744 mHandler.sendEmptyMessage(TRACK_ENDED);
1745 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1746 }
1747 };
1748
1749 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1750 public void onPrepared(MediaPlayer mp) {
1751 notifyChange(ASYNC_OPEN_COMPLETE);
1752 }
1753 };
1754
1755 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1756 public boolean onError(MediaPlayer mp, int what, int extra) {
1757 switch (what) {
1758 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1759 mIsInitialized = false;
1760 mMediaPlayer.release();
1761 // Creating a new MediaPlayer and settings its wakemode does not
1762 // require the media service, so it's OK to do this now, while the
1763 // service is still being restarted
1764 mMediaPlayer = new MediaPlayer();
1765 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1766 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1767 return true;
1768 default:
1769 break;
1770 }
1771 return false;
1772 }
1773 };
1774
1775 public long duration() {
1776 return mMediaPlayer.getDuration();
1777 }
1778
1779 public long position() {
1780 return mMediaPlayer.getCurrentPosition();
1781 }
1782
1783 public long seek(long whereto) {
1784 mMediaPlayer.seekTo((int) whereto);
1785 return whereto;
1786 }
1787
1788 public void setVolume(float vol) {
1789 mMediaPlayer.setVolume(vol, vol);
1790 }
1791 }
1792
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001793 /*
1794 * By making this a static class with a WeakReference to the Service, we
1795 * ensure that the Service can be GCd even when the system process still
1796 * has a remote reference to the stub.
1797 */
1798 static class ServiceStub extends IMediaPlaybackService.Stub {
1799 WeakReference<MediaPlaybackService> mService;
1800
1801 ServiceStub(MediaPlaybackService service) {
1802 mService = new WeakReference<MediaPlaybackService>(service);
1803 }
1804
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001805 public void openFileAsync(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001806 {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001807 mService.get().openAsync(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001808 }
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001809 public void openFile(String path, boolean oneShot)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001810 {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001811 mService.get().open(path, oneShot);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001812 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001813 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001814 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001815 }
1816 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001817 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001818 }
1819 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001820 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001821 }
1822 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001823 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001824 }
1825 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001826 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001827 }
1828 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001829 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001830 }
1831 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001832 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001833 }
1834 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001835 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001836 }
1837 public void next() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001838 mService.get().next(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001839 }
1840 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001841 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001842 }
1843 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001844 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001845 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001846 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001847 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001848 }
1849 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001850 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001851 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001852 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001853 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001854 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001855 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001856 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001857 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001858 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001859 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001860 }
1861 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001862 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001863 }
1864 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001865 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001866 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001867 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001868 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001869 }
1870 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001871 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001872 }
1873 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001874 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001875 }
1876 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001877 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001878 }
1879 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001880 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001881 }
1882 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001883 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001884 }
1885 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001886 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001887 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001888 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001889 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001890 }
1891 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001892 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001893 }
1894 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001895 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001896 }
1897 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001898 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001899 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001900
1901 }
1902
1903 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001904}