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