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