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