blob: 1d97b5d3a505e16f886ffebf15c406c498ddbca3 [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;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.app.Service;
The Android Open Source Project490384b2009-03-11 12:11:59 -070023import android.appwidget.AppWidgetManager;
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;
The Android Open Source Project792a2202009-03-03 19:32:30 -080035import android.media.AudioManager;
36import android.media.MediaFile;
37import android.media.MediaPlayer;
38import android.net.Uri;
39import android.os.Environment;
40import android.os.FileUtils;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Message;
44import android.os.PowerManager;
45import android.os.SystemClock;
46import android.os.PowerManager.WakeLock;
47import android.provider.MediaStore;
Marco Nelissen3d54a512009-06-03 16:09:18 -070048import android.telephony.PhoneStateListener;
49import android.telephony.TelephonyManager;
The Android Open Source Project792a2202009-03-03 19:32:30 -080050import android.util.Log;
51import android.widget.RemoteViews;
52import android.widget.Toast;
The Android Open Source Project792a2202009-03-03 19:32:30 -080053
54import java.io.IOException;
Marco Nelissen2b0b9132009-05-21 16:26:47 -070055import java.lang.ref.WeakReference;
The Android Open Source Project792a2202009-03-03 19:32:30 -080056import java.util.Random;
57import java.util.Vector;
58
59/**
60 * Provides "background" audio playback capabilities, allowing the
61 * user to switch between activities without stopping playback.
62 */
63public class MediaPlaybackService extends Service {
64 /** used to specify whether enqueue() should start playing
65 * the new list of files right away, next or once all the currently
66 * queued files have been played
67 */
68 public static final int NOW = 1;
69 public static final int NEXT = 2;
70 public static final int LAST = 3;
71 public static final int PLAYBACKSERVICE_STATUS = 1;
72
73 public static final int SHUFFLE_NONE = 0;
74 public static final int SHUFFLE_NORMAL = 1;
75 public static final int SHUFFLE_AUTO = 2;
76
77 public static final int REPEAT_NONE = 0;
78 public static final int REPEAT_CURRENT = 1;
79 public static final int REPEAT_ALL = 2;
80
81 public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
82 public static final String META_CHANGED = "com.android.music.metachanged";
83 public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
84 public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
85 public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
86
87 public static final String SERVICECMD = "com.android.music.musicservicecommand";
88 public static final String CMDNAME = "command";
89 public static final String CMDTOGGLEPAUSE = "togglepause";
90 public static final String CMDSTOP = "stop";
91 public static final String CMDPAUSE = "pause";
92 public static final String CMDPREVIOUS = "previous";
93 public static final String CMDNEXT = "next";
94
95 public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
96 public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
97 public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
98 public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
99
The Android Open Source Project792a2202009-03-03 19:32:30 -0800100 private static final int TRACK_ENDED = 1;
101 private static final int RELEASE_WAKELOCK = 2;
102 private static final int SERVER_DIED = 3;
103 private static final int FADEIN = 4;
Marco Nelissen3ec2ad92009-08-17 08:52:29 -0700104 private static final int MAX_HISTORY_SIZE = 100;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800105
106 private MultiPlayer mPlayer;
107 private String mFileToPlay;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800108 private int mShuffleMode = SHUFFLE_NONE;
109 private int mRepeatMode = REPEAT_NONE;
110 private int mMediaMountedCount = 0;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700111 private long [] mAutoShuffleList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800112 private boolean mOneShot;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700113 private long [] mPlayList = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800114 private int mPlayListLen = 0;
115 private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
116 private Cursor mCursor;
117 private int mPlayPos = -1;
118 private static final String LOGTAG = "MediaPlaybackService";
119 private final Shuffler mRand = new Shuffler();
120 private int mOpenFailedCounter = 0;
121 String[] mCursorCols = new String[] {
122 "audio._id AS _id", // index must match IDCOLIDX below
123 MediaStore.Audio.Media.ARTIST,
124 MediaStore.Audio.Media.ALBUM,
125 MediaStore.Audio.Media.TITLE,
126 MediaStore.Audio.Media.DATA,
127 MediaStore.Audio.Media.MIME_TYPE,
128 MediaStore.Audio.Media.ALBUM_ID,
129 MediaStore.Audio.Media.ARTIST_ID,
130 MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
131 MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
132 };
133 private final static int IDCOLIDX = 0;
134 private final static int PODCASTCOLIDX = 8;
135 private final static int BOOKMARKCOLIDX = 9;
136 private BroadcastReceiver mUnmountReceiver = null;
137 private WakeLock mWakeLock;
138 private int mServiceStartId = -1;
139 private boolean mServiceInUse = false;
140 private boolean mResumeAfterCall = false;
Marco Nelissenc1333372009-05-06 12:54:47 -0700141 private boolean mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800142 private boolean mQuietMode = false;
143
144 private SharedPreferences mPreferences;
145 // We use this to distinguish between different cards when saving/restoring playlists.
146 // This will have to change if we want to support multiple simultaneous cards.
147 private int mCardId;
148
The Android Open Source Project490384b2009-03-11 12:11:59 -0700149 private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800150
151 // interval after which we stop the service when idle
152 private static final int IDLE_DELAY = 60000;
153
Marco Nelissen3d54a512009-06-03 16:09:18 -0700154 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800155 @Override
Marco Nelissen3d54a512009-06-03 16:09:18 -0700156 public void onCallStateChanged(int state, String incomingNumber) {
157 if (state == TelephonyManager.CALL_STATE_RINGING) {
158 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
159 int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
160 if (ringvolume > 0) {
161 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
162 pause();
163 }
164 } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
165 // pause the music while a conversation is in progress
166 mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
167 pause();
168 } else if (state == TelephonyManager.CALL_STATE_IDLE) {
169 // start playing again
170 if (mResumeAfterCall) {
171 // resume playback only if music was playing
172 // when the call was answered
173 startAndFadeIn();
174 mResumeAfterCall = false;
175 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800176 }
177 }
178 };
179
180 private void startAndFadeIn() {
181 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
182 }
183
184 private Handler mMediaplayerHandler = new Handler() {
185 float mCurrentVolume = 1.0f;
186 @Override
187 public void handleMessage(Message msg) {
188 switch (msg.what) {
189 case FADEIN:
190 if (!isPlaying()) {
191 mCurrentVolume = 0f;
192 mPlayer.setVolume(mCurrentVolume);
193 play();
194 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
195 } else {
196 mCurrentVolume += 0.01f;
197 if (mCurrentVolume < 1.0f) {
198 mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
199 } else {
200 mCurrentVolume = 1.0f;
201 }
202 mPlayer.setVolume(mCurrentVolume);
203 }
204 break;
205 case SERVER_DIED:
Marco Nelissenc1333372009-05-06 12:54:47 -0700206 if (mIsSupposedToBePlaying) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800207 next(true);
208 } else {
209 // the server died when we were idle, so just
210 // reopen the same song (it will start again
211 // from the beginning though when the user
212 // restarts)
213 openCurrent();
214 }
215 break;
216 case TRACK_ENDED:
217 if (mRepeatMode == REPEAT_CURRENT) {
218 seek(0);
219 play();
220 } else if (!mOneShot) {
221 next(false);
222 } else {
223 notifyChange(PLAYBACK_COMPLETE);
Marco Nelissenc1333372009-05-06 12:54:47 -0700224 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800225 }
226 break;
227 case RELEASE_WAKELOCK:
228 mWakeLock.release();
229 break;
230 default:
231 break;
232 }
233 }
234 };
235
236 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
237 @Override
238 public void onReceive(Context context, Intent intent) {
239 String action = intent.getAction();
240 String cmd = intent.getStringExtra("command");
241 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
242 next(true);
243 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
244 prev();
245 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
246 if (isPlaying()) {
247 pause();
248 } else {
249 play();
250 }
251 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
252 pause();
253 } else if (CMDSTOP.equals(cmd)) {
254 pause();
255 seek(0);
The Android Open Source Project490384b2009-03-11 12:11:59 -0700256 } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
257 // Someone asked us to refresh a set of specific widgets, probably
The Android Open Source Project792a2202009-03-03 19:32:30 -0800258 // because they were just added.
The Android Open Source Project490384b2009-03-11 12:11:59 -0700259 int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
260 mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800261 }
262 }
263 };
264
265 public MediaPlaybackService() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800266 }
267
268 @Override
269 public void onCreate() {
270 super.onCreate();
271
272 mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
273 mCardId = FileUtils.getFatVolumeId(Environment.getExternalStorageDirectory().getPath());
274
275 registerExternalStorageListener();
276
277 // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
278 mPlayer = new MultiPlayer();
279 mPlayer.setHandler(mMediaplayerHandler);
280
281 // Clear leftover notification in case this service previously got killed while playing
282 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
283 nm.cancel(PLAYBACKSERVICE_STATUS);
284
285 reloadQueue();
286
287 IntentFilter commandFilter = new IntentFilter();
288 commandFilter.addAction(SERVICECMD);
289 commandFilter.addAction(TOGGLEPAUSE_ACTION);
290 commandFilter.addAction(PAUSE_ACTION);
291 commandFilter.addAction(NEXT_ACTION);
292 commandFilter.addAction(PREVIOUS_ACTION);
293 registerReceiver(mIntentReceiver, commandFilter);
294
Marco Nelissen3d54a512009-06-03 16:09:18 -0700295 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
296 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800297 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
298 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
299 mWakeLock.setReferenceCounted(false);
300
301 // If the service was idle, but got killed before it stopped itself, the
302 // system will relaunch it. Make sure it gets stopped again in that case.
303 Message msg = mDelayedStopHandler.obtainMessage();
304 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
305 }
306
307 @Override
308 public void onDestroy() {
309 // Check that we're not being destroyed while something is still playing.
310 if (isPlaying()) {
311 Log.e("MediaPlaybackService", "Service being destroyed while still playing.");
312 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700313 // release all MediaPlayer resources, including the native player and wakelocks
314 mPlayer.release();
315 mPlayer = null;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800316
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700317 // make sure there aren't any other messages coming
318 mDelayedStopHandler.removeCallbacksAndMessages(null);
Marco Nelissen49e36ea2009-05-28 10:20:02 -0700319 mMediaplayerHandler.removeCallbacksAndMessages(null);
Marco Nelissen2b0b9132009-05-21 16:26:47 -0700320
The Android Open Source Project792a2202009-03-03 19:32:30 -0800321 if (mCursor != null) {
322 mCursor.close();
323 mCursor = null;
324 }
325
Marco Nelissen3d54a512009-06-03 16:09:18 -0700326 TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
327 tmgr.listen(mPhoneStateListener, 0);
328
The Android Open Source Project792a2202009-03-03 19:32:30 -0800329 unregisterReceiver(mIntentReceiver);
330 if (mUnmountReceiver != null) {
331 unregisterReceiver(mUnmountReceiver);
332 mUnmountReceiver = null;
333 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800334 mWakeLock.release();
335 super.onDestroy();
336 }
337
338 private final char hexdigits [] = new char [] {
339 '0', '1', '2', '3',
340 '4', '5', '6', '7',
341 '8', '9', 'a', 'b',
342 'c', 'd', 'e', 'f'
343 };
344
345 private void saveQueue(boolean full) {
346 if (mOneShot) {
347 return;
348 }
349 Editor ed = mPreferences.edit();
350 //long start = System.currentTimeMillis();
351 if (full) {
352 StringBuilder q = new StringBuilder();
353
354 // The current playlist is saved as a list of "reverse hexadecimal"
355 // numbers, which we can generate faster than normal decimal or
356 // hexadecimal numbers, which in turn allows us to save the playlist
357 // more often without worrying too much about performance.
358 // (saving the full state takes about 40 ms under no-load conditions
359 // on the phone)
360 int len = mPlayListLen;
361 for (int i = 0; i < len; i++) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700362 long n = mPlayList[i];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800363 if (n == 0) {
364 q.append("0;");
365 } else {
366 while (n != 0) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700367 int digit = (int)(n & 0xf);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800368 n >>= 4;
369 q.append(hexdigits[digit]);
370 }
371 q.append(";");
372 }
373 }
374 //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
375 ed.putString("queue", q.toString());
376 ed.putInt("cardid", mCardId);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700377 if (mShuffleMode != SHUFFLE_NONE) {
378 // In shuffle mode we need to save the history too
379 len = mHistory.size();
380 q.setLength(0);
381 for (int i = 0; i < len; i++) {
382 int n = mHistory.get(i);
383 if (n == 0) {
384 q.append("0;");
385 } else {
386 while (n != 0) {
387 int digit = (n & 0xf);
388 n >>= 4;
389 q.append(hexdigits[digit]);
390 }
391 q.append(";");
392 }
393 }
394 ed.putString("history", q.toString());
395 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800396 }
397 ed.putInt("curpos", mPlayPos);
398 if (mPlayer.isInitialized()) {
399 ed.putLong("seekpos", mPlayer.position());
400 }
401 ed.putInt("repeatmode", mRepeatMode);
402 ed.putInt("shufflemode", mShuffleMode);
403 ed.commit();
404
405 //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
406 }
407
408 private void reloadQueue() {
409 String q = null;
410
411 boolean newstyle = false;
412 int id = mCardId;
413 if (mPreferences.contains("cardid")) {
414 newstyle = true;
415 id = mPreferences.getInt("cardid", ~mCardId);
416 }
417 if (id == mCardId) {
418 // Only restore the saved playlist if the card is still
419 // the same one as when the playlist was saved
420 q = mPreferences.getString("queue", "");
421 }
Marco Nelissen6c615a22009-06-10 12:43:25 -0700422 int qlen = q != null ? q.length() : 0;
423 if (qlen > 1) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800424 //Log.i("@@@@ service", "loaded queue: " + q);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700425 int plen = 0;
426 int n = 0;
427 int shift = 0;
428 for (int i = 0; i < qlen; i++) {
429 char c = q.charAt(i);
430 if (c == ';') {
431 ensurePlayListCapacity(plen + 1);
432 mPlayList[plen] = n;
433 plen++;
434 n = 0;
435 shift = 0;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800436 } else {
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700437 if (c >= '0' && c <= '9') {
438 n += ((c - '0') << shift);
439 } else if (c >= 'a' && c <= 'f') {
440 n += ((10 + c - 'a') << shift);
441 } else {
442 // bogus playlist data
443 plen = 0;
444 break;
445 }
446 shift += 4;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800447 }
448 }
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700449 mPlayListLen = plen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800450
451 int pos = mPreferences.getInt("curpos", 0);
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700452 if (pos < 0 || pos >= mPlayListLen) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800453 // The saved playlist is bogus, discard it
454 mPlayListLen = 0;
455 return;
456 }
457 mPlayPos = pos;
458
459 // When reloadQueue is called in response to a card-insertion,
460 // we might not be able to query the media provider right away.
461 // To deal with this, try querying for the current file, and if
462 // that fails, wait a while and try again. If that too fails,
463 // assume there is a problem and don't restore the state.
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700464 Cursor crsr = MusicUtils.query(this,
The Android Open Source Project792a2202009-03-03 19:32:30 -0800465 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
466 new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700467 if (crsr == null || crsr.getCount() == 0) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800468 // wait a bit and try again
469 SystemClock.sleep(3000);
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700470 crsr = getContentResolver().query(
The Android Open Source Project792a2202009-03-03 19:32:30 -0800471 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
472 mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
473 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700474 if (crsr != null) {
475 crsr.close();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800476 }
477
478 // Make sure we don't auto-skip to the next song, since that
479 // also starts playback. What could happen in that case is:
480 // - music is paused
481 // - go to UMS and delete some files, including the currently playing one
482 // - come back from UMS
483 // (time passes)
484 // - music app is killed for some reason (out of memory)
485 // - music service is restarted, service restores state, doesn't find
486 // the "current" file, goes to the next and: playback starts on its
487 // own, potentially at some random inconvenient time.
488 mOpenFailedCounter = 20;
489 mQuietMode = true;
490 openCurrent();
491 mQuietMode = false;
492 if (!mPlayer.isInitialized()) {
493 // couldn't restore the saved state
494 mPlayListLen = 0;
495 return;
496 }
497
498 long seekpos = mPreferences.getLong("seekpos", 0);
499 seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
500
501 int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
502 if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
503 repmode = REPEAT_NONE;
504 }
505 mRepeatMode = repmode;
506
507 int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
508 if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
509 shufmode = SHUFFLE_NONE;
510 }
Marco Nelissen63dbbbb2009-08-17 14:17:27 -0700511 if (shufmode != SHUFFLE_NONE) {
512 // in shuffle mode we need to restore the history too
513 q = mPreferences.getString("history", "");
514 qlen = q != null ? q.length() : 0;
515 if (qlen > 1) {
516 plen = 0;
517 n = 0;
518 shift = 0;
519 mHistory.clear();
520 for (int i = 0; i < qlen; i++) {
521 char c = q.charAt(i);
522 if (c == ';') {
523 if (n >= mPlayListLen) {
524 // bogus history data
525 mHistory.clear();
526 break;
527 }
528 mHistory.add(n);
529 n = 0;
530 shift = 0;
531 } else {
532 if (c >= '0' && c <= '9') {
533 n += ((c - '0') << shift);
534 } else if (c >= 'a' && c <= 'f') {
535 n += ((10 + c - 'a') << shift);
536 } else {
537 // bogus history data
538 mHistory.clear();
539 break;
540 }
541 shift += 4;
542 }
543 }
544 }
545 }
The Android Open Source Project792a2202009-03-03 19:32:30 -0800546 if (shufmode == SHUFFLE_AUTO) {
547 if (! makeAutoShuffleList()) {
548 shufmode = SHUFFLE_NONE;
549 }
550 }
551 mShuffleMode = shufmode;
552 }
553 }
554
555 @Override
556 public IBinder onBind(Intent intent) {
557 mDelayedStopHandler.removeCallbacksAndMessages(null);
558 mServiceInUse = true;
559 return mBinder;
560 }
561
562 @Override
563 public void onRebind(Intent intent) {
564 mDelayedStopHandler.removeCallbacksAndMessages(null);
565 mServiceInUse = true;
566 }
567
568 @Override
569 public void onStart(Intent intent, int startId) {
570 mServiceStartId = startId;
571 mDelayedStopHandler.removeCallbacksAndMessages(null);
572
573 String action = intent.getAction();
574 String cmd = intent.getStringExtra("command");
575
576 if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
577 next(true);
578 } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
579 prev();
580 } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
581 if (isPlaying()) {
582 pause();
583 } else {
584 play();
585 }
586 } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
587 pause();
588 } else if (CMDSTOP.equals(cmd)) {
589 pause();
590 seek(0);
591 }
592
593 // make sure the service will shut down on its own if it was
594 // just started but not bound to and nothing is playing
595 mDelayedStopHandler.removeCallbacksAndMessages(null);
596 Message msg = mDelayedStopHandler.obtainMessage();
597 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
598 }
599
600 @Override
601 public boolean onUnbind(Intent intent) {
602 mServiceInUse = false;
603
604 // Take a snapshot of the current playlist
605 saveQueue(true);
606
607 if (isPlaying() || mResumeAfterCall) {
608 // something is currently playing, or will be playing once
609 // an in-progress call ends, so don't stop the service now.
610 return true;
611 }
612
613 // If there is a playlist but playback is paused, then wait a while
614 // before stopping the service, so that pause/resume isn't slow.
615 // Also delay stopping the service if we're transitioning between tracks.
616 if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
617 Message msg = mDelayedStopHandler.obtainMessage();
618 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
619 return true;
620 }
621
622 // No active playlist, OK to stop the service right now
623 stopSelf(mServiceStartId);
624 return true;
625 }
626
627 private Handler mDelayedStopHandler = new Handler() {
628 @Override
629 public void handleMessage(Message msg) {
630 // Check again to make sure nothing is playing right now
631 if (isPlaying() || mResumeAfterCall || mServiceInUse
632 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
633 return;
634 }
635 // save the queue again, because it might have changed
636 // since the user exited the music app (because of
637 // party-shuffle or because the play-position changed)
638 saveQueue(true);
639 stopSelf(mServiceStartId);
640 }
641 };
642
643 /**
644 * Called when we receive a ACTION_MEDIA_EJECT notification.
645 *
646 * @param storagePath path to mount point for the removed media
647 */
648 public void closeExternalStorageFiles(String storagePath) {
649 // stop playback and clean up if the SD card is going to be unmounted.
650 stop(true);
651 notifyChange(QUEUE_CHANGED);
652 notifyChange(META_CHANGED);
653 }
654
655 /**
656 * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
657 * The intent will call closeExternalStorageFiles() if the external media
658 * is going to be ejected, so applications can clean up any files they have open.
659 */
660 public void registerExternalStorageListener() {
661 if (mUnmountReceiver == null) {
662 mUnmountReceiver = new BroadcastReceiver() {
663 @Override
664 public void onReceive(Context context, Intent intent) {
665 String action = intent.getAction();
666 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
667 saveQueue(true);
668 mOneShot = true; // This makes us not save the state again later,
669 // which would be wrong because the song ids and
670 // card id might not match.
671 closeExternalStorageFiles(intent.getData().getPath());
672 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
673 mMediaMountedCount++;
674 mCardId = FileUtils.getFatVolumeId(intent.getData().getPath());
675 reloadQueue();
676 notifyChange(QUEUE_CHANGED);
677 notifyChange(META_CHANGED);
678 }
679 }
680 };
681 IntentFilter iFilter = new IntentFilter();
682 iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
683 iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
684 iFilter.addDataScheme("file");
685 registerReceiver(mUnmountReceiver, iFilter);
686 }
687 }
688
689 /**
690 * Notify the change-receivers that something has changed.
691 * The intent that is sent contains the following data
692 * for the currently playing track:
693 * "id" - Integer: the database row ID
694 * "artist" - String: the name of the artist
695 * "album" - String: the name of the album
696 * "track" - String: the name of the track
697 * The intent has an action that is one of
698 * "com.android.music.metachanged"
699 * "com.android.music.queuechanged",
700 * "com.android.music.playbackcomplete"
701 * "com.android.music.playstatechanged"
702 * respectively indicating that a new track has
703 * started playing, that the playback queue has
704 * changed, that playback has stopped because
705 * the last file in the list has been played,
706 * or that the play-state changed (paused/resumed).
707 */
708 private void notifyChange(String what) {
709
710 Intent i = new Intent(what);
Marco Nelissenbd447b62009-06-29 14:52:05 -0700711 i.putExtra("id", Long.valueOf(getAudioId()));
The Android Open Source Project792a2202009-03-03 19:32:30 -0800712 i.putExtra("artist", getArtistName());
713 i.putExtra("album",getAlbumName());
714 i.putExtra("track", getTrackName());
715 sendBroadcast(i);
716
717 if (what.equals(QUEUE_CHANGED)) {
718 saveQueue(true);
719 } else {
720 saveQueue(false);
721 }
722
The Android Open Source Project490384b2009-03-11 12:11:59 -0700723 // Share this notification directly with our widgets
724 mAppWidgetProvider.notifyChange(this, what);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800725 }
726
727 private void ensurePlayListCapacity(int size) {
728 if (mPlayList == null || size > mPlayList.length) {
729 // reallocate at 2x requested size so we don't
730 // need to grow and copy the array for every
731 // insert
Marco Nelissenbd447b62009-06-29 14:52:05 -0700732 long [] newlist = new long[size * 2];
Marco Nelissenbf0ea142009-06-05 12:46:09 -0700733 int len = mPlayList != null ? mPlayList.length : mPlayListLen;
The Android Open Source Project792a2202009-03-03 19:32:30 -0800734 for (int i = 0; i < len; i++) {
735 newlist[i] = mPlayList[i];
736 }
737 mPlayList = newlist;
738 }
739 // FIXME: shrink the array when the needed size is much smaller
740 // than the allocated size
741 }
742
743 // insert the list of songs at the specified position in the playlist
Marco Nelissenbd447b62009-06-29 14:52:05 -0700744 private void addToPlayList(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800745 int addlen = list.length;
746 if (position < 0) { // overwrite
747 mPlayListLen = 0;
748 position = 0;
749 }
750 ensurePlayListCapacity(mPlayListLen + addlen);
751 if (position > mPlayListLen) {
752 position = mPlayListLen;
753 }
754
755 // move part of list after insertion point
756 int tailsize = mPlayListLen - position;
757 for (int i = tailsize ; i > 0 ; i--) {
758 mPlayList[position + i] = mPlayList[position + i - addlen];
759 }
760
761 // copy list into playlist
762 for (int i = 0; i < addlen; i++) {
763 mPlayList[position + i] = list[i];
764 }
765 mPlayListLen += addlen;
766 }
767
768 /**
769 * Appends a list of tracks to the current playlist.
770 * If nothing is playing currently, playback will be started at
771 * the first track.
772 * If the action is NOW, playback will switch to the first of
773 * the new tracks immediately.
774 * @param list The list of tracks to append.
775 * @param action NOW, NEXT or LAST
776 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700777 public void enqueue(long [] list, int action) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800778 synchronized(this) {
779 if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
780 addToPlayList(list, mPlayPos + 1);
781 notifyChange(QUEUE_CHANGED);
782 } else {
783 // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
784 addToPlayList(list, Integer.MAX_VALUE);
785 notifyChange(QUEUE_CHANGED);
786 if (action == NOW) {
787 mPlayPos = mPlayListLen - list.length;
788 openCurrent();
789 play();
790 notifyChange(META_CHANGED);
791 return;
792 }
793 }
794 if (mPlayPos < 0) {
795 mPlayPos = 0;
796 openCurrent();
797 play();
798 notifyChange(META_CHANGED);
799 }
800 }
801 }
802
803 /**
804 * Replaces the current playlist with a new list,
805 * and prepares for starting playback at the specified
806 * position in the list, or a random position if the
807 * specified position is 0.
808 * @param list The new list of tracks.
809 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700810 public void open(long [] list, int position) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800811 synchronized (this) {
812 if (mShuffleMode == SHUFFLE_AUTO) {
813 mShuffleMode = SHUFFLE_NORMAL;
814 }
Marco Nelissenbd447b62009-06-29 14:52:05 -0700815 long oldId = getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -0800816 int listlength = list.length;
817 boolean newlist = true;
818 if (mPlayListLen == listlength) {
819 // possible fast path: list might be the same
820 newlist = false;
821 for (int i = 0; i < listlength; i++) {
822 if (list[i] != mPlayList[i]) {
823 newlist = true;
824 break;
825 }
826 }
827 }
828 if (newlist) {
829 addToPlayList(list, -1);
830 notifyChange(QUEUE_CHANGED);
831 }
832 int oldpos = mPlayPos;
833 if (position >= 0) {
834 mPlayPos = position;
835 } else {
836 mPlayPos = mRand.nextInt(mPlayListLen);
837 }
838 mHistory.clear();
839
840 saveBookmarkIfNeeded();
841 openCurrent();
Jeffrey Sharkeyd8c69672009-03-24 17:59:05 -0700842 if (oldId != getAudioId()) {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800843 notifyChange(META_CHANGED);
844 }
845 }
846 }
847
848 /**
849 * Moves the item at index1 to index2.
850 * @param index1
851 * @param index2
852 */
853 public void moveQueueItem(int index1, int index2) {
854 synchronized (this) {
855 if (index1 >= mPlayListLen) {
856 index1 = mPlayListLen - 1;
857 }
858 if (index2 >= mPlayListLen) {
859 index2 = mPlayListLen - 1;
860 }
861 if (index1 < index2) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700862 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800863 for (int i = index1; i < index2; i++) {
864 mPlayList[i] = mPlayList[i+1];
865 }
866 mPlayList[index2] = tmp;
867 if (mPlayPos == index1) {
868 mPlayPos = index2;
869 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
870 mPlayPos--;
871 }
872 } else if (index2 < index1) {
Marco Nelissenbd447b62009-06-29 14:52:05 -0700873 long tmp = mPlayList[index1];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800874 for (int i = index1; i > index2; i--) {
875 mPlayList[i] = mPlayList[i-1];
876 }
877 mPlayList[index2] = tmp;
878 if (mPlayPos == index1) {
879 mPlayPos = index2;
880 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
881 mPlayPos++;
882 }
883 }
884 notifyChange(QUEUE_CHANGED);
885 }
886 }
887
888 /**
889 * Returns the current play list
890 * @return An array of integers containing the IDs of the tracks in the play list
891 */
Marco Nelissenbd447b62009-06-29 14:52:05 -0700892 public long [] getQueue() {
The Android Open Source Project792a2202009-03-03 19:32:30 -0800893 synchronized (this) {
894 int len = mPlayListLen;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700895 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -0800896 for (int i = 0; i < len; i++) {
897 list[i] = mPlayList[i];
898 }
899 return list;
900 }
901 }
902
903 private void openCurrent() {
904 synchronized (this) {
905 if (mCursor != null) {
906 mCursor.close();
907 mCursor = null;
908 }
909 if (mPlayListLen == 0) {
910 return;
911 }
912 stop(false);
913
914 String id = String.valueOf(mPlayList[mPlayPos]);
915
916 mCursor = getContentResolver().query(
917 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
918 mCursorCols, "_id=" + id , null, null);
919 if (mCursor != null) {
920 mCursor.moveToFirst();
921 open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
922 // go to bookmark if needed
923 if (isPodcast()) {
924 long bookmark = getBookmark();
925 // Start playing a little bit before the bookmark,
926 // so it's easier to get back in to the narrative.
927 seek(bookmark - 5000);
928 }
929 }
930 }
931 }
932
933 public void openAsync(String path) {
934 synchronized (this) {
935 if (path == null) {
936 return;
937 }
938
939 mRepeatMode = REPEAT_NONE;
940 ensurePlayListCapacity(1);
941 mPlayListLen = 1;
942 mPlayPos = -1;
943
944 mFileToPlay = path;
945 mCursor = null;
946 mPlayer.setDataSourceAsync(mFileToPlay);
947 mOneShot = true;
948 }
949 }
950
951 /**
952 * Opens the specified file and readies it for playback.
953 *
954 * @param path The full path of the file to be opened.
955 * @param oneshot when set to true, playback will stop after this file completes, instead
956 * of moving on to the next track in the list
957 */
958 public void open(String path, boolean oneshot) {
959 synchronized (this) {
960 if (path == null) {
961 return;
962 }
963
964 if (oneshot) {
965 mRepeatMode = REPEAT_NONE;
966 ensurePlayListCapacity(1);
967 mPlayListLen = 1;
968 mPlayPos = -1;
969 }
970
971 // if mCursor is null, try to associate path with a database cursor
972 if (mCursor == null) {
973
974 ContentResolver resolver = getContentResolver();
975 Uri uri;
976 String where;
977 String selectionArgs[];
978 if (path.startsWith("content://media/")) {
979 uri = Uri.parse(path);
980 where = null;
981 selectionArgs = null;
982 } else {
983 uri = MediaStore.Audio.Media.getContentUriForPath(path);
984 where = MediaStore.Audio.Media.DATA + "=?";
985 selectionArgs = new String[] { path };
986 }
987
988 try {
989 mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
990 if (mCursor != null) {
991 if (mCursor.getCount() == 0) {
992 mCursor.close();
993 mCursor = null;
994 } else {
995 mCursor.moveToNext();
996 ensurePlayListCapacity(1);
997 mPlayListLen = 1;
Marco Nelissenbd447b62009-06-29 14:52:05 -0700998 mPlayList[0] = mCursor.getLong(IDCOLIDX);
The Android Open Source Project792a2202009-03-03 19:32:30 -0800999 mPlayPos = 0;
1000 }
1001 }
1002 } catch (UnsupportedOperationException ex) {
1003 }
1004 }
1005 mFileToPlay = path;
1006 mPlayer.setDataSource(mFileToPlay);
1007 mOneShot = oneshot;
1008 if (! mPlayer.isInitialized()) {
1009 stop(true);
1010 if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
1011 // beware: this ends up being recursive because next() calls open() again.
1012 next(false);
1013 }
1014 if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
1015 // need to make sure we only shows this once
1016 mOpenFailedCounter = 0;
1017 if (!mQuietMode) {
1018 Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
1019 }
1020 }
1021 } else {
1022 mOpenFailedCounter = 0;
1023 }
1024 }
1025 }
1026
1027 /**
1028 * Starts playback of a previously opened file.
1029 */
1030 public void play() {
1031 if (mPlayer.isInitialized()) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001032 // if we are at the end of the song, go to the next song first
Marco Nelissen2f9a1ce2009-06-26 14:23:31 -07001033 long duration = mPlayer.duration();
1034 if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
1035 mPlayer.position() >= duration - 2000) {
Thomas Tuttle272eb782009-01-28 21:06:46 -05001036 next(true);
1037 }
1038
The Android Open Source Project792a2202009-03-03 19:32:30 -08001039 mPlayer.start();
1040 setForeground(true);
1041
1042 NotificationManager nm = (NotificationManager)
1043 getSystemService(Context.NOTIFICATION_SERVICE);
1044
1045 RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
1046 views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
1047 if (getAudioId() < 0) {
1048 // streaming
1049 views.setTextViewText(R.id.trackname, getPath());
1050 views.setTextViewText(R.id.artistalbum, null);
1051 } else {
1052 String artist = getArtistName();
1053 views.setTextViewText(R.id.trackname, getTrackName());
1054 if (artist == null || artist.equals(MediaFile.UNKNOWN_STRING)) {
1055 artist = getString(R.string.unknown_artist_name);
1056 }
1057 String album = getAlbumName();
1058 if (album == null || album.equals(MediaFile.UNKNOWN_STRING)) {
1059 album = getString(R.string.unknown_album_name);
1060 }
1061
1062 views.setTextViewText(R.id.artistalbum,
1063 getString(R.string.notification_artist_album, artist, album)
1064 );
1065 }
1066
The Android Open Source Project792a2202009-03-03 19:32:30 -08001067 Notification status = new Notification();
1068 status.contentView = views;
1069 status.flags |= Notification.FLAG_ONGOING_EVENT;
1070 status.icon = R.drawable.stat_notify_musicplayer;
1071 status.contentIntent = PendingIntent.getActivity(this, 0,
1072 new Intent("com.android.music.PLAYBACK_VIEWER"), 0);
1073 nm.notify(PLAYBACKSERVICE_STATUS, status);
Marco Nelissenc1333372009-05-06 12:54:47 -07001074 if (!mIsSupposedToBePlaying) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001075 notifyChange(PLAYSTATE_CHANGED);
1076 }
Marco Nelissenc1333372009-05-06 12:54:47 -07001077 mIsSupposedToBePlaying = true;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001078 } else if (mPlayListLen <= 0) {
1079 // This is mostly so that if you press 'play' on a bluetooth headset
1080 // without every having played anything before, it will still play
1081 // something.
1082 setShuffleMode(SHUFFLE_AUTO);
1083 }
1084 }
1085
1086 private void stop(boolean remove_status_icon) {
1087 if (mPlayer.isInitialized()) {
1088 mPlayer.stop();
1089 }
1090 mFileToPlay = null;
1091 if (mCursor != null) {
1092 mCursor.close();
1093 mCursor = null;
1094 }
1095 if (remove_status_icon) {
1096 gotoIdleState();
1097 }
1098 setForeground(false);
1099 if (remove_status_icon) {
Marco Nelissenc1333372009-05-06 12:54:47 -07001100 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001101 }
1102 }
1103
1104 /**
1105 * Stops playback.
1106 */
1107 public void stop() {
1108 stop(true);
1109 }
1110
1111 /**
1112 * Pauses playback (call play() to resume)
1113 */
1114 public void pause() {
Marco Nelissen407cf912009-05-11 09:56:31 -07001115 synchronized(this) {
1116 if (isPlaying()) {
1117 mPlayer.pause();
1118 gotoIdleState();
1119 setForeground(false);
1120 mIsSupposedToBePlaying = false;
1121 notifyChange(PLAYSTATE_CHANGED);
1122 saveBookmarkIfNeeded();
1123 }
The Android Open Source Project792a2202009-03-03 19:32:30 -08001124 }
1125 }
1126
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001127 /** Returns whether something is currently playing
The Android Open Source Project792a2202009-03-03 19:32:30 -08001128 *
Marco Nelissenb6e7bf72009-05-11 10:28:31 -07001129 * @return true if something is playing (or will be playing shortly, in case
1130 * we're currently transitioning between tracks), false if not.
The Android Open Source Project792a2202009-03-03 19:32:30 -08001131 */
1132 public boolean isPlaying() {
Marco Nelissenc1333372009-05-06 12:54:47 -07001133 return mIsSupposedToBePlaying;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001134 }
1135
1136 /*
1137 Desired behavior for prev/next/shuffle:
1138
1139 - NEXT will move to the next track in the list when not shuffling, and to
1140 a track randomly picked from the not-yet-played tracks when shuffling.
1141 If all tracks have already been played, pick from the full set, but
1142 avoid picking the previously played track if possible.
1143 - when shuffling, PREV will go to the previously played track. Hitting PREV
1144 again will go to the track played before that, etc. When the start of the
1145 history has been reached, PREV is a no-op.
1146 When not shuffling, PREV will go to the sequentially previous track (the
1147 difference with the shuffle-case is mainly that when not shuffling, the
1148 user can back up to tracks that are not in the history).
1149
1150 Example:
1151 When playing an album with 10 tracks from the start, and enabling shuffle
1152 while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
1153 the final play order might be 1-2-3-4-5-8-10-6-9-7.
1154 When hitting 'prev' 8 times while playing track 7 in this example, the
1155 user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
1156 a random track will be picked again. If at any time user disables shuffling
1157 the next/previous track will be picked in sequential order again.
1158 */
1159
1160 public void prev() {
1161 synchronized (this) {
1162 if (mOneShot) {
1163 // we were playing a specific file not part of a playlist, so there is no 'previous'
1164 seek(0);
1165 play();
1166 return;
1167 }
1168 if (mShuffleMode == SHUFFLE_NORMAL) {
1169 // go to previously-played track and remove it from the history
1170 int histsize = mHistory.size();
1171 if (histsize == 0) {
1172 // prev is a no-op
1173 return;
1174 }
1175 Integer pos = mHistory.remove(histsize - 1);
1176 mPlayPos = pos.intValue();
1177 } else {
1178 if (mPlayPos > 0) {
1179 mPlayPos--;
1180 } else {
1181 mPlayPos = mPlayListLen - 1;
1182 }
1183 }
1184 saveBookmarkIfNeeded();
1185 stop(false);
1186 openCurrent();
1187 play();
1188 notifyChange(META_CHANGED);
1189 }
1190 }
1191
1192 public void next(boolean force) {
1193 synchronized (this) {
1194 if (mOneShot) {
1195 // we were playing a specific file not part of a playlist, so there is no 'next'
1196 seek(0);
1197 play();
1198 return;
1199 }
1200
Marco Nelissen663fea32009-06-12 13:39:27 -07001201 if (mPlayListLen <= 0) {
1202 return;
1203 }
1204
The Android Open Source Project792a2202009-03-03 19:32:30 -08001205 // Store the current file in the history, but keep the history at a
1206 // reasonable size
1207 if (mPlayPos >= 0) {
1208 mHistory.add(Integer.valueOf(mPlayPos));
1209 }
1210 if (mHistory.size() > MAX_HISTORY_SIZE) {
1211 mHistory.removeElementAt(0);
1212 }
1213
1214 if (mShuffleMode == SHUFFLE_NORMAL) {
1215 // Pick random next track from the not-yet-played ones
1216 // TODO: make it work right after adding/removing items in the queue.
1217
1218 int numTracks = mPlayListLen;
1219 int[] tracks = new int[numTracks];
1220 for (int i=0;i < numTracks; i++) {
1221 tracks[i] = i;
1222 }
1223
1224 int numHistory = mHistory.size();
1225 int numUnplayed = numTracks;
1226 for (int i=0;i < numHistory; i++) {
1227 int idx = mHistory.get(i).intValue();
1228 if (idx < numTracks && tracks[idx] >= 0) {
1229 numUnplayed--;
1230 tracks[idx] = -1;
1231 }
1232 }
1233
1234 // 'numUnplayed' now indicates how many tracks have not yet
1235 // been played, and 'tracks' contains the indices of those
1236 // tracks.
1237 if (numUnplayed <=0) {
1238 // everything's already been played
1239 if (mRepeatMode == REPEAT_ALL || force) {
1240 //pick from full set
1241 numUnplayed = numTracks;
1242 for (int i=0;i < numTracks; i++) {
1243 tracks[i] = i;
1244 }
1245 } else {
1246 // all done
1247 gotoIdleState();
1248 return;
1249 }
1250 }
1251 int skip = mRand.nextInt(numUnplayed);
1252 int cnt = -1;
1253 while (true) {
1254 while (tracks[++cnt] < 0)
1255 ;
1256 skip--;
1257 if (skip < 0) {
1258 break;
1259 }
1260 }
1261 mPlayPos = cnt;
1262 } else if (mShuffleMode == SHUFFLE_AUTO) {
1263 doAutoShuffleUpdate();
1264 mPlayPos++;
1265 } else {
1266 if (mPlayPos >= mPlayListLen - 1) {
1267 // we're at the end of the list
1268 if (mRepeatMode == REPEAT_NONE && !force) {
1269 // all done
1270 gotoIdleState();
1271 notifyChange(PLAYBACK_COMPLETE);
Marco Nelissenc1333372009-05-06 12:54:47 -07001272 mIsSupposedToBePlaying = false;
The Android Open Source Project792a2202009-03-03 19:32:30 -08001273 return;
1274 } else if (mRepeatMode == REPEAT_ALL || force) {
1275 mPlayPos = 0;
1276 }
1277 } else {
1278 mPlayPos++;
1279 }
1280 }
1281 saveBookmarkIfNeeded();
1282 stop(false);
1283 openCurrent();
1284 play();
1285 notifyChange(META_CHANGED);
1286 }
1287 }
1288
1289 private void gotoIdleState() {
1290 NotificationManager nm =
1291 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
1292 nm.cancel(PLAYBACKSERVICE_STATUS);
1293 mDelayedStopHandler.removeCallbacksAndMessages(null);
1294 Message msg = mDelayedStopHandler.obtainMessage();
1295 mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
1296 }
1297
1298 private void saveBookmarkIfNeeded() {
1299 try {
1300 if (isPodcast()) {
1301 long pos = position();
1302 long bookmark = getBookmark();
1303 long duration = duration();
1304 if ((pos < bookmark && (pos + 10000) > bookmark) ||
1305 (pos > bookmark && (pos - 10000) < bookmark)) {
1306 // The existing bookmark is close to the current
1307 // position, so don't update it.
1308 return;
1309 }
1310 if (pos < 15000 || (pos + 10000) > duration) {
1311 // if we're near the start or end, clear the bookmark
1312 pos = 0;
1313 }
1314
1315 // write 'pos' to the bookmark field
1316 ContentValues values = new ContentValues();
1317 values.put(MediaStore.Audio.Media.BOOKMARK, pos);
1318 Uri uri = ContentUris.withAppendedId(
1319 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mCursor.getLong(IDCOLIDX));
1320 getContentResolver().update(uri, values, null, null);
1321 }
1322 } catch (SQLiteException ex) {
1323 }
1324 }
1325
1326 // Make sure there are at least 5 items after the currently playing item
1327 // and no more than 10 items before.
1328 private void doAutoShuffleUpdate() {
1329 boolean notify = false;
1330 // remove old entries
1331 if (mPlayPos > 10) {
1332 removeTracks(0, mPlayPos - 9);
1333 notify = true;
1334 }
1335 // add new entries if needed
1336 int to_add = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
1337 for (int i = 0; i < to_add; i++) {
1338 // pick something at random from the list
1339 int idx = mRand.nextInt(mAutoShuffleList.length);
Marco Nelissenbd447b62009-06-29 14:52:05 -07001340 long which = mAutoShuffleList[idx];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001341 ensurePlayListCapacity(mPlayListLen + 1);
1342 mPlayList[mPlayListLen++] = which;
1343 notify = true;
1344 }
1345 if (notify) {
1346 notifyChange(QUEUE_CHANGED);
1347 }
1348 }
1349
1350 // A simple variation of Random that makes sure that the
1351 // value it returns is not equal to the value it returned
1352 // previously, unless the interval is 1.
Marco Nelissen756c3f52009-05-14 10:07:23 -07001353 private static class Shuffler {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001354 private int mPrevious;
1355 private Random mRandom = new Random();
1356 public int nextInt(int interval) {
1357 int ret;
1358 do {
1359 ret = mRandom.nextInt(interval);
1360 } while (ret == mPrevious && interval > 1);
1361 mPrevious = ret;
1362 return ret;
1363 }
1364 };
1365
1366 private boolean makeAutoShuffleList() {
1367 ContentResolver res = getContentResolver();
1368 Cursor c = null;
1369 try {
1370 c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1371 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
1372 null, null);
1373 if (c == null || c.getCount() == 0) {
1374 return false;
1375 }
1376 int len = c.getCount();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001377 long [] list = new long[len];
The Android Open Source Project792a2202009-03-03 19:32:30 -08001378 for (int i = 0; i < len; i++) {
1379 c.moveToNext();
Marco Nelissenbd447b62009-06-29 14:52:05 -07001380 list[i] = c.getLong(0);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001381 }
1382 mAutoShuffleList = list;
1383 return true;
1384 } catch (RuntimeException ex) {
1385 } finally {
1386 if (c != null) {
1387 c.close();
1388 }
1389 }
1390 return false;
1391 }
1392
1393 /**
1394 * Removes the range of tracks specified from the play list. If a file within the range is
1395 * the file currently being played, playback will move to the next file after the
1396 * range.
1397 * @param first The first file to be removed
1398 * @param last The last file to be removed
1399 * @return the number of tracks deleted
1400 */
1401 public int removeTracks(int first, int last) {
1402 int numremoved = removeTracksInternal(first, last);
1403 if (numremoved > 0) {
1404 notifyChange(QUEUE_CHANGED);
1405 }
1406 return numremoved;
1407 }
1408
1409 private int removeTracksInternal(int first, int last) {
1410 synchronized (this) {
1411 if (last < first) return 0;
1412 if (first < 0) first = 0;
1413 if (last >= mPlayListLen) last = mPlayListLen - 1;
1414
1415 boolean gotonext = false;
1416 if (first <= mPlayPos && mPlayPos <= last) {
1417 mPlayPos = first;
1418 gotonext = true;
1419 } else if (mPlayPos > last) {
1420 mPlayPos -= (last - first + 1);
1421 }
1422 int num = mPlayListLen - last - 1;
1423 for (int i = 0; i < num; i++) {
1424 mPlayList[first + i] = mPlayList[last + 1 + i];
1425 }
1426 mPlayListLen -= last - first + 1;
1427
1428 if (gotonext) {
1429 if (mPlayListLen == 0) {
1430 stop(true);
1431 mPlayPos = -1;
1432 } else {
1433 if (mPlayPos >= mPlayListLen) {
1434 mPlayPos = 0;
1435 }
1436 boolean wasPlaying = isPlaying();
1437 stop(false);
1438 openCurrent();
1439 if (wasPlaying) {
1440 play();
1441 }
1442 }
1443 }
1444 return last - first + 1;
1445 }
1446 }
1447
1448 /**
1449 * Removes all instances of the track with the given id
1450 * from the playlist.
1451 * @param id The id to be removed
1452 * @return how many instances of the track were removed
1453 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001454 public int removeTrack(long id) {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001455 int numremoved = 0;
1456 synchronized (this) {
1457 for (int i = 0; i < mPlayListLen; i++) {
1458 if (mPlayList[i] == id) {
1459 numremoved += removeTracksInternal(i, i);
1460 i--;
1461 }
1462 }
1463 }
1464 if (numremoved > 0) {
1465 notifyChange(QUEUE_CHANGED);
1466 }
1467 return numremoved;
1468 }
1469
1470 public void setShuffleMode(int shufflemode) {
1471 synchronized(this) {
1472 if (mShuffleMode == shufflemode && mPlayListLen > 0) {
1473 return;
1474 }
1475 mShuffleMode = shufflemode;
1476 if (mShuffleMode == SHUFFLE_AUTO) {
1477 if (makeAutoShuffleList()) {
1478 mPlayListLen = 0;
1479 doAutoShuffleUpdate();
1480 mPlayPos = 0;
1481 openCurrent();
1482 play();
1483 notifyChange(META_CHANGED);
1484 return;
1485 } else {
1486 // failed to build a list of files to shuffle
1487 mShuffleMode = SHUFFLE_NONE;
1488 }
1489 }
1490 saveQueue(false);
1491 }
1492 }
1493 public int getShuffleMode() {
1494 return mShuffleMode;
1495 }
1496
1497 public void setRepeatMode(int repeatmode) {
1498 synchronized(this) {
1499 mRepeatMode = repeatmode;
1500 saveQueue(false);
1501 }
1502 }
1503 public int getRepeatMode() {
1504 return mRepeatMode;
1505 }
1506
1507 public int getMediaMountedCount() {
1508 return mMediaMountedCount;
1509 }
1510
1511 /**
1512 * Returns the path of the currently playing file, or null if
1513 * no file is currently playing.
1514 */
1515 public String getPath() {
1516 return mFileToPlay;
1517 }
1518
1519 /**
1520 * Returns the rowid of the currently playing file, or -1 if
1521 * no file is currently playing.
1522 */
Marco Nelissenbd447b62009-06-29 14:52:05 -07001523 public long getAudioId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001524 synchronized (this) {
1525 if (mPlayPos >= 0 && mPlayer.isInitialized()) {
1526 return mPlayList[mPlayPos];
1527 }
1528 }
1529 return -1;
1530 }
1531
1532 /**
1533 * Returns the position in the queue
1534 * @return the position in the queue
1535 */
1536 public int getQueuePosition() {
1537 synchronized(this) {
1538 return mPlayPos;
1539 }
1540 }
1541
1542 /**
1543 * Starts playing the track at the given position in the queue.
1544 * @param pos The position in the queue of the track that will be played.
1545 */
1546 public void setQueuePosition(int pos) {
1547 synchronized(this) {
1548 stop(false);
1549 mPlayPos = pos;
1550 openCurrent();
1551 play();
1552 notifyChange(META_CHANGED);
1553 }
1554 }
1555
1556 public String getArtistName() {
1557 synchronized(this) {
1558 if (mCursor == null) {
1559 return null;
1560 }
1561 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
1562 }
1563 }
1564
Marco Nelissenbd447b62009-06-29 14:52:05 -07001565 public long getArtistId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001566 synchronized (this) {
1567 if (mCursor == null) {
1568 return -1;
1569 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001570 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001571 }
1572 }
1573
1574 public String getAlbumName() {
1575 synchronized (this) {
1576 if (mCursor == null) {
1577 return null;
1578 }
1579 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
1580 }
1581 }
1582
Marco Nelissenbd447b62009-06-29 14:52:05 -07001583 public long getAlbumId() {
The Android Open Source Project792a2202009-03-03 19:32:30 -08001584 synchronized (this) {
1585 if (mCursor == null) {
1586 return -1;
1587 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001588 return mCursor.getLong(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
The Android Open Source Project792a2202009-03-03 19:32:30 -08001589 }
1590 }
1591
1592 public String getTrackName() {
1593 synchronized (this) {
1594 if (mCursor == null) {
1595 return null;
1596 }
1597 return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
1598 }
1599 }
1600
1601 private boolean isPodcast() {
1602 synchronized (this) {
1603 if (mCursor == null) {
1604 return false;
1605 }
1606 return (mCursor.getInt(PODCASTCOLIDX) > 0);
1607 }
1608 }
1609
1610 private long getBookmark() {
1611 synchronized (this) {
1612 if (mCursor == null) {
1613 return 0;
1614 }
1615 return mCursor.getLong(BOOKMARKCOLIDX);
1616 }
1617 }
1618
1619 /**
1620 * Returns the duration of the file in milliseconds.
1621 * Currently this method returns -1 for the duration of MIDI files.
1622 */
1623 public long duration() {
1624 if (mPlayer.isInitialized()) {
1625 return mPlayer.duration();
1626 }
1627 return -1;
1628 }
1629
1630 /**
1631 * Returns the current playback position in milliseconds
1632 */
1633 public long position() {
1634 if (mPlayer.isInitialized()) {
1635 return mPlayer.position();
1636 }
1637 return -1;
1638 }
1639
1640 /**
1641 * Seeks to the position specified.
1642 *
1643 * @param pos The position to seek to, in milliseconds
1644 */
1645 public long seek(long pos) {
1646 if (mPlayer.isInitialized()) {
1647 if (pos < 0) pos = 0;
1648 if (pos > mPlayer.duration()) pos = mPlayer.duration();
1649 return mPlayer.seek(pos);
1650 }
1651 return -1;
1652 }
1653
1654 /**
1655 * Provides a unified interface for dealing with midi files and
1656 * other media files.
1657 */
1658 private class MultiPlayer {
1659 private MediaPlayer mMediaPlayer = new MediaPlayer();
1660 private Handler mHandler;
1661 private boolean mIsInitialized = false;
1662
1663 public MultiPlayer() {
1664 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1665 }
1666
1667 public void setDataSourceAsync(String path) {
1668 try {
1669 mMediaPlayer.reset();
1670 mMediaPlayer.setDataSource(path);
1671 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1672 mMediaPlayer.setOnPreparedListener(preparedlistener);
1673 mMediaPlayer.prepareAsync();
1674 } catch (IOException ex) {
1675 // TODO: notify the user why the file couldn't be opened
1676 mIsInitialized = false;
1677 return;
1678 } catch (IllegalArgumentException ex) {
1679 // TODO: notify the user why the file couldn't be opened
1680 mIsInitialized = false;
1681 return;
1682 }
1683 mMediaPlayer.setOnCompletionListener(listener);
1684 mMediaPlayer.setOnErrorListener(errorListener);
1685
1686 mIsInitialized = true;
1687 }
1688
1689 public void setDataSource(String path) {
1690 try {
1691 mMediaPlayer.reset();
1692 mMediaPlayer.setOnPreparedListener(null);
1693 if (path.startsWith("content://")) {
1694 mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
1695 } else {
1696 mMediaPlayer.setDataSource(path);
1697 }
1698 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
1699 mMediaPlayer.prepare();
1700 } catch (IOException ex) {
1701 // TODO: notify the user why the file couldn't be opened
1702 mIsInitialized = false;
1703 return;
1704 } catch (IllegalArgumentException ex) {
1705 // TODO: notify the user why the file couldn't be opened
1706 mIsInitialized = false;
1707 return;
1708 }
1709 mMediaPlayer.setOnCompletionListener(listener);
1710 mMediaPlayer.setOnErrorListener(errorListener);
1711
1712 mIsInitialized = true;
1713 }
1714
1715 public boolean isInitialized() {
1716 return mIsInitialized;
1717 }
1718
1719 public void start() {
1720 mMediaPlayer.start();
1721 }
1722
1723 public void stop() {
1724 mMediaPlayer.reset();
1725 mIsInitialized = false;
1726 }
1727
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001728 /**
1729 * You CANNOT use this player anymore after calling release()
1730 */
1731 public void release() {
1732 stop();
1733 mMediaPlayer.release();
1734 }
1735
The Android Open Source Project792a2202009-03-03 19:32:30 -08001736 public void pause() {
1737 mMediaPlayer.pause();
1738 }
1739
The Android Open Source Project792a2202009-03-03 19:32:30 -08001740 public void setHandler(Handler handler) {
1741 mHandler = handler;
1742 }
1743
1744 MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
1745 public void onCompletion(MediaPlayer mp) {
1746 // Acquire a temporary wakelock, since when we return from
1747 // this callback the MediaPlayer will release its wakelock
1748 // and allow the device to go to sleep.
1749 // This temporary wakelock is released when the RELEASE_WAKELOCK
1750 // message is processed, but just in case, put a timeout on it.
1751 mWakeLock.acquire(30000);
1752 mHandler.sendEmptyMessage(TRACK_ENDED);
1753 mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
1754 }
1755 };
1756
1757 MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
1758 public void onPrepared(MediaPlayer mp) {
1759 notifyChange(ASYNC_OPEN_COMPLETE);
1760 }
1761 };
1762
1763 MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
1764 public boolean onError(MediaPlayer mp, int what, int extra) {
1765 switch (what) {
1766 case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
1767 mIsInitialized = false;
1768 mMediaPlayer.release();
1769 // Creating a new MediaPlayer and settings its wakemode does not
1770 // require the media service, so it's OK to do this now, while the
1771 // service is still being restarted
1772 mMediaPlayer = new MediaPlayer();
1773 mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
1774 mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
1775 return true;
1776 default:
1777 break;
1778 }
1779 return false;
1780 }
1781 };
1782
1783 public long duration() {
1784 return mMediaPlayer.getDuration();
1785 }
1786
1787 public long position() {
1788 return mMediaPlayer.getCurrentPosition();
1789 }
1790
1791 public long seek(long whereto) {
1792 mMediaPlayer.seekTo((int) whereto);
1793 return whereto;
1794 }
1795
1796 public void setVolume(float vol) {
1797 mMediaPlayer.setVolume(vol, vol);
1798 }
1799 }
1800
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001801 /*
1802 * By making this a static class with a WeakReference to the Service, we
1803 * ensure that the Service can be GCd even when the system process still
1804 * has a remote reference to the stub.
1805 */
1806 static class ServiceStub extends IMediaPlaybackService.Stub {
1807 WeakReference<MediaPlaybackService> mService;
1808
1809 ServiceStub(MediaPlaybackService service) {
1810 mService = new WeakReference<MediaPlaybackService>(service);
1811 }
1812
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001813 public void openFileAsync(String path)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001814 {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001815 mService.get().openAsync(path);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001816 }
Marco Nelissenb7841ac2009-05-05 14:36:25 -07001817 public void openFile(String path, boolean oneShot)
The Android Open Source Project792a2202009-03-03 19:32:30 -08001818 {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001819 mService.get().open(path, oneShot);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001820 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001821 public void open(long [] list, int position) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001822 mService.get().open(list, position);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001823 }
1824 public int getQueuePosition() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001825 return mService.get().getQueuePosition();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001826 }
1827 public void setQueuePosition(int index) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001828 mService.get().setQueuePosition(index);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001829 }
1830 public boolean isPlaying() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001831 return mService.get().isPlaying();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001832 }
1833 public void stop() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001834 mService.get().stop();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001835 }
1836 public void pause() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001837 mService.get().pause();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001838 }
1839 public void play() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001840 mService.get().play();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001841 }
1842 public void prev() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001843 mService.get().prev();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001844 }
1845 public void next() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001846 mService.get().next(true);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001847 }
1848 public String getTrackName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001849 return mService.get().getTrackName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001850 }
1851 public String getAlbumName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001852 return mService.get().getAlbumName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001853 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001854 public long getAlbumId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001855 return mService.get().getAlbumId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001856 }
1857 public String getArtistName() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001858 return mService.get().getArtistName();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001859 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001860 public long getArtistId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001861 return mService.get().getArtistId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001862 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001863 public void enqueue(long [] list , int action) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001864 mService.get().enqueue(list, action);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001865 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001866 public long [] getQueue() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001867 return mService.get().getQueue();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001868 }
1869 public void moveQueueItem(int from, int to) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001870 mService.get().moveQueueItem(from, to);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001871 }
1872 public String getPath() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001873 return mService.get().getPath();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001874 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001875 public long getAudioId() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001876 return mService.get().getAudioId();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001877 }
1878 public long position() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001879 return mService.get().position();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001880 }
1881 public long duration() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001882 return mService.get().duration();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001883 }
1884 public long seek(long pos) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001885 return mService.get().seek(pos);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001886 }
1887 public void setShuffleMode(int shufflemode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001888 mService.get().setShuffleMode(shufflemode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001889 }
1890 public int getShuffleMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001891 return mService.get().getShuffleMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001892 }
1893 public int removeTracks(int first, int last) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001894 return mService.get().removeTracks(first, last);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001895 }
Marco Nelissenbd447b62009-06-29 14:52:05 -07001896 public int removeTrack(long id) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001897 return mService.get().removeTrack(id);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001898 }
1899 public void setRepeatMode(int repeatmode) {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001900 mService.get().setRepeatMode(repeatmode);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001901 }
1902 public int getRepeatMode() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001903 return mService.get().getRepeatMode();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001904 }
1905 public int getMediaMountedCount() {
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001906 return mService.get().getMediaMountedCount();
The Android Open Source Project792a2202009-03-03 19:32:30 -08001907 }
Marco Nelissen2b0b9132009-05-21 16:26:47 -07001908
1909 }
1910
1911 private final IBinder mBinder = new ServiceStub(this);
The Android Open Source Project792a2202009-03-03 19:32:30 -08001912}