PageRenderTime 78ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/cyanogenmod/eleven/MusicPlaybackService.java

https://gitlab.com/Tu-dou520/android_packages_apps_Eleven
Java | 1514 lines | 889 code | 213 blank | 412 comment | 234 complexity | c645971ff086af0e72e3aaafda585733 MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 Andrew Neal
  3. * Copyright (C) 2014 The CyanogenMod Project
  4. * Copyright (C) 2015 The SudaMod Project
  5. * Licensed under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with the
  7. * License. You may obtain a copy of the License at
  8. * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
  9. * or agreed to in writing, software distributed under the License is
  10. * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  11. * KIND, either express or implied. See the License for the specific language
  12. * governing permissions and limitations under the License.
  13. */
  14. package com.cyanogenmod.eleven;
  15. import android.annotation.SuppressLint;
  16. import android.app.AlarmManager;
  17. import android.app.Notification;
  18. import android.app.NotificationManager;
  19. import android.app.PendingIntent;
  20. import android.app.Service;
  21. import android.appwidget.AppWidgetManager;
  22. import android.content.BroadcastReceiver;
  23. import android.content.ComponentName;
  24. import android.content.ContentResolver;
  25. import android.content.Context;
  26. import android.content.Intent;
  27. import android.content.IntentFilter;
  28. import android.content.SharedPreferences;
  29. import android.database.ContentObserver;
  30. import android.database.Cursor;
  31. import android.database.MatrixCursor;
  32. import android.graphics.Bitmap;
  33. import android.hardware.SensorManager;
  34. import android.media.AudioManager;
  35. import android.media.AudioManager.OnAudioFocusChangeListener;
  36. import android.media.MediaMetadata;
  37. import android.media.MediaPlayer;
  38. import android.media.audiofx.AudioEffect;
  39. import android.media.session.MediaSession;
  40. import android.media.session.PlaybackState;
  41. import android.net.Uri;
  42. import android.os.Handler;
  43. import android.os.HandlerThread;
  44. import android.os.IBinder;
  45. import android.os.Looper;
  46. import android.os.Message;
  47. import android.os.PowerManager;
  48. import android.os.RemoteException;
  49. import android.os.SystemClock;
  50. import android.provider.MediaStore;
  51. import android.provider.MediaStore.Audio.AlbumColumns;
  52. import android.provider.MediaStore.Audio.AudioColumns;
  53. import android.text.TextUtils;
  54. import android.util.Log;
  55. import com.cyanogenmod.eleven.Config.IdType;
  56. import com.cyanogenmod.eleven.appwidgets.AppWidgetLarge;
  57. import com.cyanogenmod.eleven.appwidgets.AppWidgetLargeAlternate;
  58. import com.cyanogenmod.eleven.appwidgets.AppWidgetSmall;
  59. import com.cyanogenmod.eleven.cache.ImageCache;
  60. import com.cyanogenmod.eleven.cache.ImageFetcher;
  61. import com.cyanogenmod.eleven.provider.MusicPlaybackState;
  62. import com.cyanogenmod.eleven.provider.RecentStore;
  63. import com.cyanogenmod.eleven.provider.SongPlayCount;
  64. import com.cyanogenmod.eleven.service.MusicPlaybackTrack;
  65. import com.cyanogenmod.eleven.utils.BitmapWithColors;
  66. import com.cyanogenmod.eleven.utils.Lists;
  67. import com.cyanogenmod.eleven.utils.PreferenceUtils;
  68. import com.cyanogenmod.eleven.utils.ShakeDetector;
  69. import com.cyanogenmod.eleven.utils.SrtManager;
  70. import java.io.File;
  71. import java.io.IOException;
  72. import java.lang.ref.WeakReference;
  73. import java.util.ArrayList;
  74. import java.util.LinkedList;
  75. import java.util.ListIterator;
  76. import java.util.Random;
  77. import java.util.TreeSet;
  78. /**
  79. * A backbround {@link Service} used to keep music playing between activities
  80. * and when the user moves Apollo into the background.
  81. */
  82. @SuppressLint("NewApi")
  83. public class MusicPlaybackService extends Service {
  84. private static final String TAG = "MusicPlaybackService";
  85. private static final boolean D = false;
  86. /**
  87. * Indicates that the music has paused or resumed
  88. */
  89. public static final String PLAYSTATE_CHANGED = "com.cyanogenmod.eleven.playstatechanged";
  90. /**
  91. * Indicates that music playback position within
  92. * a title was changed
  93. */
  94. public static final String POSITION_CHANGED = "com.cyanogenmod.eleven.positionchanged";
  95. /**
  96. * Indicates the meta data has changed in some way, like a track change
  97. */
  98. public static final String META_CHANGED = "com.cyanogenmod.eleven.metachanged";
  99. /**
  100. * Indicates the queue has been updated
  101. */
  102. public static final String QUEUE_CHANGED = "com.cyanogenmod.eleven.queuechanged";
  103. /**
  104. * Indicates the queue has been updated
  105. */
  106. public static final String PLAYLIST_CHANGED = "com.cyanogenmod.eleven.playlistchanged";
  107. /**
  108. * Indicates the repeat mode changed
  109. */
  110. public static final String REPEATMODE_CHANGED = "com.cyanogenmod.eleven.repeatmodechanged";
  111. /**
  112. * Indicates the shuffle mode changed
  113. */
  114. public static final String SHUFFLEMODE_CHANGED = "com.cyanogenmod.eleven.shufflemodechanged";
  115. /**
  116. * Indicates the track fails to play
  117. */
  118. public static final String TRACK_ERROR = "com.cyanogenmod.eleven.trackerror";
  119. /**
  120. * For backwards compatibility reasons, also provide sticky
  121. * broadcasts under the music package
  122. */
  123. public static final String ELEVEN_PACKAGE_NAME = "com.cyanogenmod.eleven";
  124. public static final String MUSIC_PACKAGE_NAME = "com.android.music";
  125. /**
  126. * Called to indicate a general service commmand. Used in
  127. * {@link MediaButtonIntentReceiver}
  128. */
  129. public static final String SERVICECMD = "com.cyanogenmod.eleven.musicservicecommand";
  130. /**
  131. * Called to go toggle between pausing and playing the music
  132. */
  133. public static final String TOGGLEPAUSE_ACTION = "com.cyanogenmod.eleven.togglepause";
  134. /**
  135. * Called to go to pause the playback
  136. */
  137. public static final String PAUSE_ACTION = "com.cyanogenmod.eleven.pause";
  138. /**
  139. * Called to go to stop the playback
  140. */
  141. public static final String STOP_ACTION = "com.cyanogenmod.eleven.stop";
  142. /**
  143. * Called to go to stop the playback for sleep mode
  144. */
  145. public static final String SLEEP_MODE_STOP_ACTION = "com.sudamod.eleven.sleepmode.stop";
  146. /**
  147. * Called to go to the previous track or the beginning of the track if partway through the track
  148. */
  149. public static final String PREVIOUS_ACTION = "com.cyanogenmod.eleven.previous";
  150. /**
  151. * Called to go to the previous track regardless of how far in the current track the playback is
  152. */
  153. public static final String PREVIOUS_FORCE_ACTION = "com.cyanogenmod.eleven.previous.force";
  154. /**
  155. * Called to go to the next track
  156. */
  157. public static final String NEXT_ACTION = "com.cyanogenmod.eleven.next";
  158. /**
  159. * Called to change the repeat mode
  160. */
  161. public static final String REPEAT_ACTION = "com.cyanogenmod.eleven.repeat";
  162. /**
  163. * Called to change the shuffle mode
  164. */
  165. public static final String SHUFFLE_ACTION = "com.cyanogenmod.eleven.shuffle";
  166. public static final String FROM_MEDIA_BUTTON = "frommediabutton";
  167. /**
  168. * Used to easily notify a list that it should refresh. i.e. A playlist
  169. * changes
  170. */
  171. public static final String REFRESH = "com.cyanogenmod.eleven.refresh";
  172. /**
  173. * Used by the alarm intent to shutdown the service after being idle
  174. */
  175. private static final String SHUTDOWN = "com.cyanogenmod.eleven.shutdown";
  176. /**
  177. * Called to notify of a timed text
  178. */
  179. public static final String NEW_LYRICS = "com.cyanogenmod.eleven.lyrics";
  180. /**
  181. * Called to update the remote control client
  182. */
  183. public static final String UPDATE_LOCKSCREEN = "com.cyanogenmod.eleven.updatelockscreen";
  184. public static final String CMDNAME = "command";
  185. public static final String CMDTOGGLEPAUSE = "togglepause";
  186. public static final String CMDSTOP = "stop";
  187. public static final String CMDPAUSE = "pause";
  188. public static final String CMDPLAY = "play";
  189. public static final String CMDPREVIOUS = "previous";
  190. public static final String CMDNEXT = "next";
  191. public static final String CMDNOTIF = "buttonId";
  192. private static final int IDCOLIDX = 0;
  193. /**
  194. * Moves a list to the next position in the queue
  195. */
  196. public static final int NEXT = 2;
  197. /**
  198. * Moves a list to the last position in the queue
  199. */
  200. public static final int LAST = 3;
  201. /**
  202. * Shuffles no songs, turns shuffling off
  203. */
  204. public static final int SHUFFLE_NONE = 0;
  205. /**
  206. * Shuffles all songs
  207. */
  208. public static final int SHUFFLE_NORMAL = 1;
  209. /**
  210. * Party shuffle
  211. */
  212. public static final int SHUFFLE_AUTO = 2;
  213. /**
  214. * Turns repeat off
  215. */
  216. public static final int REPEAT_NONE = 0;
  217. /**
  218. * Repeats the current track in a list
  219. */
  220. public static final int REPEAT_CURRENT = 1;
  221. /**
  222. * Repeats all the tracks in a list
  223. */
  224. public static final int REPEAT_ALL = 2;
  225. /**
  226. * Indicates when the track ends
  227. */
  228. private static final int TRACK_ENDED = 1;
  229. /**
  230. * Indicates that the current track was changed the next track
  231. */
  232. private static final int TRACK_WENT_TO_NEXT = 2;
  233. /**
  234. * Indicates the player died
  235. */
  236. private static final int SERVER_DIED = 3;
  237. /**
  238. * Indicates some sort of focus change, maybe a phone call
  239. */
  240. private static final int FOCUSCHANGE = 4;
  241. /**
  242. * Indicates to fade the volume down
  243. */
  244. private static final int FADEDOWN = 5;
  245. /**
  246. * Indicates to fade the volume back up
  247. */
  248. private static final int FADEUP = 6;
  249. /**
  250. * Notifies that there is a new timed text string
  251. */
  252. private static final int LYRICS = 7;
  253. /**
  254. * Idle time before stopping the foreground notfication (5 minutes)
  255. */
  256. private static final int IDLE_DELAY = 5 * 60 * 1000;
  257. /**
  258. * Song play time used as threshold for rewinding to the beginning of the
  259. * track instead of skipping to the previous track when getting the PREVIOUS
  260. * command
  261. */
  262. private static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 3000;
  263. /**
  264. * The max size allowed for the track history
  265. * TODO: Comeback and rewrite/fix all the whole queue code bugs after demo
  266. * https://cyanogen.atlassian.net/browse/MUSIC-175
  267. * https://cyanogen.atlassian.net/browse/MUSIC-44
  268. */
  269. public static final int MAX_HISTORY_SIZE = 1000;
  270. public interface TrackErrorExtra {
  271. /**
  272. * Name of the track that was unable to play
  273. */
  274. public static final String TRACK_NAME = "trackname";
  275. }
  276. /**
  277. * The columns used to retrieve any info from the current track
  278. */
  279. private static final String[] PROJECTION = new String[] {
  280. "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
  281. MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
  282. MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
  283. MediaStore.Audio.Media.ARTIST_ID
  284. };
  285. /**
  286. * The columns used to retrieve any info from the current album
  287. */
  288. private static final String[] ALBUM_PROJECTION = new String[] {
  289. MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
  290. MediaStore.Audio.Albums.LAST_YEAR
  291. };
  292. /**
  293. * Keeps a mapping of the track history
  294. */
  295. private static LinkedList<Integer> mHistory = Lists.newLinkedList();
  296. /**
  297. * Used to shuffle the tracks
  298. */
  299. private static final Shuffler mShuffler = new Shuffler();
  300. /**
  301. * Service stub
  302. */
  303. private final IBinder mBinder = new ServiceStub(this);
  304. /**
  305. * 4x1 widget
  306. */
  307. private final AppWidgetSmall mAppWidgetSmall = AppWidgetSmall.getInstance();
  308. /**
  309. * 4x2 widget
  310. */
  311. private final AppWidgetLarge mAppWidgetLarge = AppWidgetLarge.getInstance();
  312. /**
  313. * 4x2 alternate widget
  314. */
  315. private final AppWidgetLargeAlternate mAppWidgetLargeAlternate = AppWidgetLargeAlternate
  316. .getInstance();
  317. /**
  318. * The media player
  319. */
  320. private MultiPlayer mPlayer;
  321. /**
  322. * The path of the current file to play
  323. */
  324. private String mFileToPlay;
  325. /**
  326. * Alarm intent for removing the notification when nothing is playing
  327. * for some time
  328. */
  329. private AlarmManager mAlarmManager;
  330. private PendingIntent mShutdownIntent;
  331. private boolean mShutdownScheduled;
  332. private NotificationManager mNotificationManager;
  333. /**
  334. * The cursor used to retrieve info on the current track and run the
  335. * necessary queries to play audio files
  336. */
  337. private Cursor mCursor;
  338. /**
  339. * The cursor used to retrieve info on the album the current track is
  340. * part of, if any.
  341. */
  342. private Cursor mAlbumCursor;
  343. /**
  344. * Monitors the audio state
  345. */
  346. private AudioManager mAudioManager;
  347. /**
  348. * Settings used to save and retrieve the queue and history
  349. */
  350. private SharedPreferences mPreferences;
  351. /**
  352. * Used to know when the service is active
  353. */
  354. private boolean mServiceInUse = false;
  355. /**
  356. * Used to know if something should be playing or not
  357. */
  358. private boolean mIsSupposedToBePlaying = false;
  359. /**
  360. * Gets the last played time to determine whether we still want notifications or not
  361. */
  362. private long mLastPlayedTime;
  363. private int mNotifyMode = NOTIFY_MODE_NONE;
  364. private long mNotificationPostTime = 0;
  365. private static final int NOTIFY_MODE_NONE = 0;
  366. private static final int NOTIFY_MODE_FOREGROUND = 1;
  367. private static final int NOTIFY_MODE_BACKGROUND = 2;
  368. /**
  369. * Used to indicate if the queue can be saved
  370. */
  371. private boolean mQueueIsSaveable = true;
  372. /**
  373. * Used to track what type of audio focus loss caused the playback to pause
  374. */
  375. private boolean mPausedByTransientLossOfFocus = false;
  376. /**
  377. * Lock screen controls
  378. */
  379. private MediaSession mSession;
  380. private ComponentName mMediaButtonReceiverComponent;
  381. // We use this to distinguish between different cards when saving/restoring
  382. // playlists
  383. private int mCardId;
  384. private int mPlayPos = -1;
  385. private int mNextPlayPos = -1;
  386. private int mOpenFailedCounter = 0;
  387. private int mMediaMountedCount = 0;
  388. private int mShuffleMode = SHUFFLE_NONE;
  389. private int mRepeatMode = REPEAT_NONE;
  390. private int mServiceStartId = -1;
  391. private String mLyrics;
  392. private ArrayList<MusicPlaybackTrack> mPlaylist = new ArrayList<MusicPlaybackTrack>(100);
  393. private long[] mAutoShuffleList = null;
  394. private MusicPlayerHandler mPlayerHandler;
  395. private HandlerThread mHandlerThread;
  396. private BroadcastReceiver mUnmountReceiver = null;
  397. // to improve perf, instead of hitting the disk cache or file cache, store the bitmaps in memory
  398. private String mCachedKey;
  399. private BitmapWithColors[] mCachedBitmapWithColors = new BitmapWithColors[2];
  400. /**
  401. * Image cache
  402. */
  403. private ImageFetcher mImageFetcher;
  404. /**
  405. * Recently listened database
  406. */
  407. private RecentStore mRecentsCache;
  408. /**
  409. * The song play count database
  410. */
  411. private SongPlayCount mSongPlayCountCache;
  412. /**
  413. * Stores the playback state
  414. */
  415. private MusicPlaybackState mPlaybackStateStore;
  416. /**
  417. * Shake detector class used for shake to switch song feature
  418. */
  419. private ShakeDetector mShakeDetector;
  420. /**
  421. * Switch for displaying album art on lockscreen
  422. */
  423. private boolean mShowAlbumArtOnLockscreen;
  424. private ShakeDetector.Listener mShakeDetectorListener=new ShakeDetector.Listener() {
  425. @Override
  426. public void hearShake() {
  427. /*
  428. * on shake detect, play next song
  429. */
  430. if (D) {
  431. Log.d(TAG,"Shake detected!!!");
  432. }
  433. gotoNext(true);
  434. }
  435. };
  436. /**
  437. * {@inheritDoc}
  438. */
  439. @Override
  440. public IBinder onBind(final Intent intent) {
  441. if (D) Log.d(TAG, "Service bound, intent = " + intent);
  442. cancelShutdown();
  443. mServiceInUse = true;
  444. return mBinder;
  445. }
  446. /**
  447. * {@inheritDoc}
  448. */
  449. @Override
  450. public boolean onUnbind(final Intent intent) {
  451. if (D) Log.d(TAG, "Service unbound");
  452. mServiceInUse = false;
  453. saveQueue(true);
  454. if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {
  455. // Something is currently playing, or will be playing once
  456. // an in-progress action requesting audio focus ends, so don't stop
  457. // the service now.
  458. return true;
  459. // If there is a playlist but playback is paused, then wait a while
  460. // before stopping the service, so that pause/resume isn't slow.
  461. // Also delay stopping the service if we're transitioning between
  462. // tracks.
  463. } else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
  464. scheduleDelayedShutdown();
  465. return true;
  466. }
  467. stopSelf(mServiceStartId);
  468. return true;
  469. }
  470. /**
  471. * {@inheritDoc}
  472. */
  473. @Override
  474. public void onRebind(final Intent intent) {
  475. cancelShutdown();
  476. mServiceInUse = true;
  477. }
  478. /**
  479. * {@inheritDoc}
  480. */
  481. @Override
  482. public void onCreate() {
  483. if (D) Log.d(TAG, "Creating service");
  484. super.onCreate();
  485. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  486. // Initialize the favorites and recents databases
  487. mRecentsCache = RecentStore.getInstance(this);
  488. // gets the song play count cache
  489. mSongPlayCountCache = SongPlayCount.getInstance(this);
  490. // gets a pointer to the playback state store
  491. mPlaybackStateStore = MusicPlaybackState.getInstance(this);
  492. // Initialize the image fetcher
  493. mImageFetcher = ImageFetcher.getInstance(this);
  494. // Initialize the image cache
  495. mImageFetcher.setImageCache(ImageCache.getInstance(this));
  496. // Start up the thread running the service. Note that we create a
  497. // separate thread because the service normally runs in the process's
  498. // main thread, which we don't want to block. We also make it
  499. // background priority so CPU-intensive work will not disrupt the UI.
  500. mHandlerThread = new HandlerThread("MusicPlayerHandler",
  501. android.os.Process.THREAD_PRIORITY_BACKGROUND);
  502. mHandlerThread.start();
  503. // Initialize the handler
  504. mPlayerHandler = new MusicPlayerHandler(this, mHandlerThread.getLooper());
  505. // Initialize the audio manager and register any headset controls for
  506. // playback
  507. mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
  508. mMediaButtonReceiverComponent = new ComponentName(getPackageName(),
  509. MediaButtonIntentReceiver.class.getName());
  510. mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
  511. // Use the remote control APIs to set the playback state
  512. setUpMediaSession();
  513. // Initialize the preferences
  514. mPreferences = getSharedPreferences("Service", 0);
  515. mCardId = getCardId();
  516. registerExternalStorageListener();
  517. // Initialize the media player
  518. mPlayer = new MultiPlayer(this);
  519. mPlayer.setHandler(mPlayerHandler);
  520. // Initialize the intent filter and each action
  521. final IntentFilter filter = new IntentFilter();
  522. filter.addAction(SERVICECMD);
  523. filter.addAction(TOGGLEPAUSE_ACTION);
  524. filter.addAction(PAUSE_ACTION);
  525. filter.addAction(STOP_ACTION);
  526. filter.addAction(SLEEP_MODE_STOP_ACTION);
  527. filter.addAction(NEXT_ACTION);
  528. filter.addAction(PREVIOUS_ACTION);
  529. filter.addAction(PREVIOUS_FORCE_ACTION);
  530. filter.addAction(REPEAT_ACTION);
  531. filter.addAction(SHUFFLE_ACTION);
  532. // Attach the broadcast listener
  533. registerReceiver(mIntentReceiver, filter);
  534. // Get events when MediaStore content changes
  535. mMediaStoreObserver = new MediaStoreObserver(mPlayerHandler);
  536. getContentResolver().registerContentObserver(
  537. MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mMediaStoreObserver);
  538. getContentResolver().registerContentObserver(
  539. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);
  540. // Initialize the delayed shutdown intent
  541. final Intent shutdownIntent = new Intent(this, MusicPlaybackService.class);
  542. shutdownIntent.setAction(SHUTDOWN);
  543. mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
  544. mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);
  545. // Listen for the idle state
  546. scheduleDelayedShutdown();
  547. // Bring the queue back
  548. reloadQueue();
  549. notifyChange(QUEUE_CHANGED);
  550. notifyChange(META_CHANGED);
  551. }
  552. private void setUpMediaSession() {
  553. mSession = new MediaSession(this, "Eleven");
  554. mSession.setCallback(new MediaSession.Callback() {
  555. @Override
  556. public void onPause() {
  557. pause();
  558. mPausedByTransientLossOfFocus = false;
  559. }
  560. @Override
  561. public void onPlay() {
  562. play();
  563. }
  564. @Override
  565. public void onSeekTo(long pos) {
  566. seek(pos);
  567. }
  568. @Override
  569. public void onSkipToNext() {
  570. gotoNext(true);
  571. }
  572. @Override
  573. public void onSkipToPrevious() {
  574. prev(false);
  575. }
  576. @Override
  577. public void onStop() {
  578. pause();
  579. mPausedByTransientLossOfFocus = false;
  580. seek(0);
  581. releaseServiceUiAndStop();
  582. }
  583. });
  584. mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
  585. }
  586. /**
  587. * {@inheritDoc}
  588. */
  589. @Override
  590. public void onDestroy() {
  591. if (D) Log.d(TAG, "Destroying service");
  592. super.onDestroy();
  593. // Remove any sound effects
  594. final Intent audioEffectsIntent = new Intent(
  595. AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
  596. audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
  597. audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
  598. sendBroadcast(audioEffectsIntent);
  599. // remove any pending alarms
  600. mAlarmManager.cancel(mShutdownIntent);
  601. // Remove any callbacks from the handler
  602. mPlayerHandler.removeCallbacksAndMessages(null);
  603. // quit the thread so that anything that gets posted won't run
  604. mHandlerThread.quitSafely();
  605. // Release the player
  606. mPlayer.release();
  607. mPlayer = null;
  608. // Remove the audio focus listener and lock screen controls
  609. mAudioManager.abandonAudioFocus(mAudioFocusListener);
  610. mSession.release();
  611. // remove the media store observer
  612. getContentResolver().unregisterContentObserver(mMediaStoreObserver);
  613. // Close the cursor
  614. closeCursor();
  615. // Unregister the mount listener
  616. unregisterReceiver(mIntentReceiver);
  617. if (mUnmountReceiver != null) {
  618. unregisterReceiver(mUnmountReceiver);
  619. mUnmountReceiver = null;
  620. }
  621. // deinitialize shake detector
  622. stopShakeDetector(true);
  623. }
  624. /**
  625. * {@inheritDoc}
  626. */
  627. @Override
  628. public int onStartCommand(final Intent intent, final int flags, final int startId) {
  629. if (D) Log.d(TAG, "Got new intent " + intent + ", startId = " + startId);
  630. mServiceStartId = startId;
  631. if (intent != null) {
  632. final String action = intent.getAction();
  633. if (SHUTDOWN.equals(action)) {
  634. mShutdownScheduled = false;
  635. releaseServiceUiAndStop();
  636. return START_NOT_STICKY;
  637. }
  638. handleCommandIntent(intent);
  639. }
  640. // Make sure the service will shut down on its own if it was
  641. // just started but not bound to and nothing is playing
  642. scheduleDelayedShutdown();
  643. if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {
  644. MediaButtonIntentReceiver.completeWakefulIntent(intent);
  645. }
  646. return START_STICKY;
  647. }
  648. private void releaseServiceUiAndStop() {
  649. if (isPlaying()
  650. || mPausedByTransientLossOfFocus
  651. || mPlayerHandler.hasMessages(TRACK_ENDED)) {
  652. return;
  653. }
  654. if (D) Log.d(TAG, "Nothing is playing anymore, releasing notification");
  655. cancelNotification();
  656. mAudioManager.abandonAudioFocus(mAudioFocusListener);
  657. mSession.setActive(false);
  658. if (!mServiceInUse) {
  659. saveQueue(true);
  660. stopSelf(mServiceStartId);
  661. }
  662. }
  663. private void handleCommandIntent(Intent intent) {
  664. final String action = intent.getAction();
  665. final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;
  666. if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
  667. if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
  668. gotoNext(true);
  669. } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
  670. || PREVIOUS_FORCE_ACTION.equals(action)) {
  671. prev(PREVIOUS_FORCE_ACTION.equals(action));
  672. } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
  673. if (isPlaying()) {
  674. pause();
  675. mPausedByTransientLossOfFocus = false;
  676. } else {
  677. play();
  678. }
  679. } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
  680. pause();
  681. mPausedByTransientLossOfFocus = false;
  682. } else if (CMDPLAY.equals(command)) {
  683. play();
  684. } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
  685. pause();
  686. mPausedByTransientLossOfFocus = false;
  687. seek(0);
  688. releaseServiceUiAndStop();
  689. } else if (SLEEP_MODE_STOP_ACTION.equals(action)) {
  690. setSleepMode(false);
  691. pause();
  692. mPausedByTransientLossOfFocus = false;
  693. seek(0);
  694. releaseServiceUiAndStop();
  695. } else if (REPEAT_ACTION.equals(action)) {
  696. cycleRepeat();
  697. } else if (SHUFFLE_ACTION.equals(action)) {
  698. cycleShuffle();
  699. }
  700. }
  701. /**
  702. * Updates the notification, considering the current play and activity state
  703. */
  704. private void updateNotification() {
  705. final int newNotifyMode;
  706. if (isPlaying()) {
  707. newNotifyMode = NOTIFY_MODE_FOREGROUND;
  708. } else if (recentlyPlayed()) {
  709. newNotifyMode = NOTIFY_MODE_BACKGROUND;
  710. } else {
  711. newNotifyMode = NOTIFY_MODE_NONE;
  712. }
  713. int notificationId = hashCode();
  714. if (mNotifyMode != newNotifyMode) {
  715. if (mNotifyMode == NOTIFY_MODE_FOREGROUND) {
  716. stopForeground(newNotifyMode == NOTIFY_MODE_NONE);
  717. } else if (newNotifyMode == NOTIFY_MODE_NONE) {
  718. mNotificationManager.cancel(notificationId);
  719. mNotificationPostTime = 0;
  720. }
  721. }
  722. if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
  723. startForeground(notificationId, buildNotification());
  724. } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
  725. mNotificationManager.notify(notificationId, buildNotification());
  726. }
  727. mNotifyMode = newNotifyMode;
  728. }
  729. private void cancelNotification() {
  730. stopForeground(true);
  731. mNotificationManager.cancel(hashCode());
  732. mNotificationPostTime = 0;
  733. mNotifyMode = NOTIFY_MODE_NONE;
  734. }
  735. /**
  736. * @return A card ID used to save and restore playlists, i.e., the queue.
  737. */
  738. private int getCardId() {
  739. final ContentResolver resolver = getContentResolver();
  740. Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
  741. null, null);
  742. int mCardId = -1;
  743. if (cursor != null && cursor.moveToFirst()) {
  744. mCardId = cursor.getInt(0);
  745. cursor.close();
  746. cursor = null;
  747. }
  748. return mCardId;
  749. }
  750. /**
  751. * Called when we receive a ACTION_MEDIA_EJECT notification.
  752. *
  753. * @param storagePath The path to mount point for the removed media
  754. */
  755. public void closeExternalStorageFiles(final String storagePath) {
  756. stop(true);
  757. notifyChange(QUEUE_CHANGED);
  758. notifyChange(META_CHANGED);
  759. }
  760. /**
  761. * Registers an intent to listen for ACTION_MEDIA_EJECT notifications. The
  762. * intent will call closeExternalStorageFiles() if the external media is
  763. * going to be ejected, so applications can clean up any files they have
  764. * open.
  765. */
  766. public void registerExternalStorageListener() {
  767. if (mUnmountReceiver == null) {
  768. mUnmountReceiver = new BroadcastReceiver() {
  769. /**
  770. * {@inheritDoc}
  771. */
  772. @Override
  773. public void onReceive(final Context context, final Intent intent) {
  774. final String action = intent.getAction();
  775. if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
  776. saveQueue(true);
  777. mQueueIsSaveable = false;
  778. closeExternalStorageFiles(intent.getData().getPath());
  779. } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
  780. mMediaMountedCount++;
  781. mCardId = getCardId();
  782. reloadQueue();
  783. mQueueIsSaveable = true;
  784. notifyChange(QUEUE_CHANGED);
  785. notifyChange(META_CHANGED);
  786. }
  787. }
  788. };
  789. final IntentFilter filter = new IntentFilter();
  790. filter.addAction(Intent.ACTION_MEDIA_EJECT);
  791. filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
  792. filter.addDataScheme("file");
  793. registerReceiver(mUnmountReceiver, filter);
  794. }
  795. }
  796. private void scheduleDelayedShutdown() {
  797. if (D) Log.v(TAG, "Scheduling shutdown in " + IDLE_DELAY + " ms");
  798. mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
  799. SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);
  800. mShutdownScheduled = true;
  801. }
  802. private void cancelShutdown() {
  803. if (D) Log.d(TAG, "Cancelling delayed shutdown, scheduled = " + mShutdownScheduled);
  804. if (mShutdownScheduled) {
  805. mAlarmManager.cancel(mShutdownIntent);
  806. mShutdownScheduled = false;
  807. }
  808. }
  809. /**
  810. * Stops playback
  811. *
  812. * @param goToIdle True to go to the idle state, false otherwise
  813. */
  814. private void stop(final boolean goToIdle) {
  815. if (D) Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
  816. if (mPlayer.isInitialized()) {
  817. mPlayer.stop();
  818. }
  819. mFileToPlay = null;
  820. closeCursor();
  821. if (goToIdle) {
  822. setIsSupposedToBePlaying(false, false);
  823. } else {
  824. stopForeground(false);
  825. }
  826. }
  827. /**
  828. * Removes the range of tracks specified from the play list. If a file
  829. * within the range is the file currently being played, playback will move
  830. * to the next file after the range.
  831. *
  832. * @param first The first file to be removed
  833. * @param last The last file to be removed
  834. * @return the number of tracks deleted
  835. */
  836. private int removeTracksInternal(int first, int last) {
  837. synchronized (this) {
  838. if (last < first) {
  839. return 0;
  840. } else if (first < 0) {
  841. first = 0;
  842. } else if (last >= mPlaylist.size()) {
  843. last = mPlaylist.size() - 1;
  844. }
  845. boolean gotonext = false;
  846. if (first <= mPlayPos && mPlayPos <= last) {
  847. mPlayPos = first;
  848. gotonext = true;
  849. } else if (mPlayPos > last) {
  850. mPlayPos -= last - first + 1;
  851. }
  852. final int numToRemove = last - first + 1;
  853. if (first == 0 && last == mPlaylist.size() - 1) {
  854. mPlayPos = -1;
  855. mNextPlayPos = -1;
  856. mPlaylist.clear();
  857. mHistory.clear();
  858. } else {
  859. for (int i = 0; i < numToRemove; i++) {
  860. mPlaylist.remove(first);
  861. }
  862. // remove the items from the history
  863. // this is not ideal as the history shouldn't be impacted by this
  864. // but since we are removing items from the array, it will throw
  865. // an exception if we keep it around. Idealistically with the queue
  866. // rewrite this should be all be fixed
  867. // https://cyanogen.atlassian.net/browse/MUSIC-44
  868. ListIterator<Integer> positionIterator = mHistory.listIterator();
  869. while (positionIterator.hasNext()) {
  870. int pos = positionIterator.next();
  871. if (pos >= first && pos <= last) {
  872. positionIterator.remove();
  873. } else if (pos > last) {
  874. positionIterator.set(pos - numToRemove);
  875. }
  876. }
  877. }
  878. if (gotonext) {
  879. if (mPlaylist.size() == 0) {
  880. stop(true);
  881. mPlayPos = -1;
  882. closeCursor();
  883. } else {
  884. if (mShuffleMode != SHUFFLE_NONE) {
  885. mPlayPos = getNextPosition(true);
  886. } else if (mPlayPos >= mPlaylist.size()) {
  887. mPlayPos = 0;
  888. }
  889. final boolean wasPlaying = isPlaying();
  890. stop(false);
  891. openCurrentAndNext();
  892. if (wasPlaying) {
  893. play();
  894. }
  895. }
  896. notifyChange(META_CHANGED);
  897. }
  898. return last - first + 1;
  899. }
  900. }
  901. /**
  902. * Adds a list to the playlist
  903. *
  904. * @param list The list to add
  905. * @param position The position to place the tracks
  906. */
  907. private void addToPlayList(final long[] list, int position, long sourceId, IdType sourceType) {
  908. final int addlen = list.length;
  909. if (position < 0) {
  910. mPlaylist.clear();
  911. position = 0;
  912. }
  913. mPlaylist.ensureCapacity(mPlaylist.size() + addlen);
  914. if (position > mPlaylist.size()) {
  915. position = mPlaylist.size();
  916. }
  917. final ArrayList<MusicPlaybackTrack> arrayList = new ArrayList<MusicPlaybackTrack>(addlen);
  918. for (int i = 0; i < list.length; i++) {
  919. arrayList.add(new MusicPlaybackTrack(list[i], sourceId, sourceType, i));
  920. }
  921. mPlaylist.addAll(position, arrayList);
  922. if (mPlaylist.size() == 0) {
  923. closeCursor();
  924. notifyChange(META_CHANGED);
  925. }
  926. }
  927. /**
  928. * @param trackId The track ID
  929. */
  930. private void updateCursor(final long trackId) {
  931. updateCursor("_id=" + trackId, null);
  932. }
  933. private void updateCursor(final String selection, final String[] selectionArgs) {
  934. synchronized (this) {
  935. closeCursor();
  936. mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  937. PROJECTION, selection, selectionArgs);
  938. }
  939. updateAlbumCursor();
  940. }
  941. private void updateCursor(final Uri uri) {
  942. synchronized (this) {
  943. closeCursor();
  944. mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);
  945. }
  946. updateAlbumCursor();
  947. }
  948. private void updateAlbumCursor() {
  949. long albumId = getAlbumId();
  950. if (albumId >= 0) {
  951. mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
  952. ALBUM_PROJECTION, "_id=" + albumId, null);
  953. } else {
  954. mAlbumCursor = null;
  955. }
  956. }
  957. private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,
  958. String selection, String[] selectionArgs) {
  959. Cursor c = getContentResolver().query(uri, projection,
  960. selection, selectionArgs, null, null);
  961. if (c == null) {
  962. return null;
  963. }
  964. if (!c.moveToFirst()) {
  965. c.close();
  966. return null;
  967. }
  968. return c;
  969. }
  970. private synchronized void closeCursor() {
  971. if (mCursor != null) {
  972. mCursor.close();
  973. mCursor = null;
  974. }
  975. if (mAlbumCursor != null) {
  976. mAlbumCursor.close();
  977. mAlbumCursor = null;
  978. }
  979. }
  980. /**
  981. * Called to open a new file as the current track and prepare the next for
  982. * playback
  983. */
  984. private void openCurrentAndNext() {
  985. openCurrentAndMaybeNext(true);
  986. }
  987. /**
  988. * Called to open a new file as the current track and prepare the next for
  989. * playback
  990. *
  991. * @param openNext True to prepare the next track for playback, false
  992. * otherwise.
  993. */
  994. private void openCurrentAndMaybeNext(final boolean openNext) {
  995. synchronized (this) {
  996. closeCursor();
  997. if (mPlaylist.size() == 0) {
  998. return;
  999. }
  1000. stop(false);
  1001. boolean shutdown = false;
  1002. updateCursor(mPlaylist.get(mPlayPos).mId);
  1003. while (true) {
  1004. if (mCursor != null
  1005. && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
  1006. + mCursor.getLong(IDCOLIDX))) {
  1007. break;
  1008. }
  1009. // if we get here then opening the file failed. We can close the
  1010. // cursor now, because
  1011. // we're either going to create a new one next, or stop trying
  1012. closeCursor();
  1013. if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {
  1014. final int pos = getNextPosition(false);
  1015. if (pos < 0) {
  1016. shutdown = true;
  1017. break;
  1018. }
  1019. mPlayPos = pos;
  1020. stop(false);
  1021. mPlayPos = pos;
  1022. updateCursor(mPlaylist.get(mPlayPos).mId);
  1023. } else {
  1024. mOpenFailedCounter = 0;
  1025. Log.w(TAG, "Failed to open file for playback");
  1026. shutdown = true;
  1027. break;
  1028. }
  1029. }
  1030. if (shutdown) {
  1031. scheduleDelayedShutdown();
  1032. if (mIsSupposedToBePlaying) {
  1033. mIsSupposedToBePlaying = false;
  1034. notifyChange(PLAYSTATE_CHANGED);
  1035. }
  1036. } else if (openNext) {
  1037. setNextTrack();
  1038. }
  1039. }
  1040. }
  1041. private void sendErrorMessage(final String trackName) {
  1042. final Intent i = new Intent(TRACK_ERROR);
  1043. i.putExtra(TrackErrorExtra.TRACK_NAME, trackName);
  1044. sendBroadcast(i);
  1045. }
  1046. /**
  1047. * @param force True to force the player onto the track next, false
  1048. * otherwise.
  1049. * @param saveToHistory True to save the mPlayPos to the history
  1050. * @return The next position to play.
  1051. */
  1052. private int getNextPosition(final boolean force) {
  1053. // as a base case, if the playlist is empty just return -1
  1054. if (mPlaylist == null || mPlaylist.isEmpty()) {
  1055. return -1;
  1056. }
  1057. // if we're not forced to go to the next track and we are only playing the current track
  1058. if (!force && mRepeatMode == REPEAT_CURRENT) {
  1059. if (mPlayPos < 0) {
  1060. return 0;
  1061. }
  1062. return mPlayPos;
  1063. } else if (mShuffleMode == SHUFFLE_NORMAL) {
  1064. final int numTracks = mPlaylist.size();
  1065. // count the number of times a track has been played
  1066. final int[] trackNumPlays = new int[numTracks];
  1067. for (int i = 0; i < numTracks; i++) {
  1068. // set it all to 0
  1069. trackNumPlays[i] = 0;
  1070. }
  1071. // walk through the history and add up the number of times the track
  1072. // has been played
  1073. final int numHistory = mHistory.size();
  1074. for (int i = 0; i < numHistory; i++) {
  1075. final int idx = mHistory.get(i).intValue();
  1076. if (idx >= 0 && idx < numTracks) {
  1077. trackNumPlays[idx]++;
  1078. }
  1079. }
  1080. // also add the currently playing track to the count
  1081. if (mPlayPos >= 0 && mPlayPos < numTracks) {
  1082. trackNumPlays[mPlayPos]++;
  1083. }
  1084. // figure out the least # of times a track has a played as well as
  1085. // how many tracks share that count
  1086. int minNumPlays = Integer.MAX_VALUE;
  1087. int numTracksWithMinNumPlays = 0;
  1088. for (int i = 0; i < trackNumPlays.length; i++) {
  1089. // if we found a new track that has less number of plays, reset the counters
  1090. if (trackNumPlays[i] < minNumPlays) {
  1091. minNumPlays = trackNumPlays[i];
  1092. numTracksWithMinNumPlays = 1;
  1093. } else if (trackNumPlays[i] == minNumPlays) {
  1094. // increment this track shares the # of tracks
  1095. numTracksWithMinNumPlays++;
  1096. }
  1097. }
  1098. // if we've played each track at least once and all tracks have been played an equal
  1099. // # of times and we aren't repeating all and we're not forcing a track, then
  1100. // return no more tracks
  1101. if (minNumPlays > 0 && numTracksWithMinNumPlays == numTracks
  1102. && mRepeatMode != REPEAT_ALL && !force) {
  1103. return -1;
  1104. }
  1105. // else pick a track from the least number of played tracks
  1106. int skip = mShuffler.nextInt(numTracksWithMinNumPlays);
  1107. for (int i = 0; i < trackNumPlays.length; i++) {
  1108. if (trackNumPlays[i] == minNumPlays) {
  1109. if (skip == 0) {
  1110. return i;
  1111. } else {
  1112. skip--;
  1113. }
  1114. }
  1115. }
  1116. // Unexpected to land here
  1117. if (D) Log.e(TAG, "Getting the next position resulted did not get a result when it should have");
  1118. return -1;
  1119. } else if (mShuffleMode == SHUFFLE_AUTO) {
  1120. doAutoShuffleUpdate();
  1121. return mPlayPos + 1;
  1122. } else {
  1123. if (mPlayPos >= mPlaylist.size() - 1) {
  1124. if (mRepeatMode == REPEAT_NONE && !force) {
  1125. return -1;
  1126. } else if (mRepeatMode == REPEAT_ALL || force) {
  1127. return 0;
  1128. }
  1129. return -1;
  1130. } else {
  1131. return mPlayPos + 1;
  1132. }
  1133. }
  1134. }
  1135. /**
  1136. * Sets the track to be played
  1137. */
  1138. private void setNextTrack() {
  1139. setNextTrack(getNextPosition(false));
  1140. }
  1141. /**
  1142. * Sets the next track to be played
  1143. * @param position the target position we want
  1144. */
  1145. private void setNextTrack(int position) {
  1146. mNextPlayPos = position;
  1147. if (D) Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
  1148. if (mNextPlayPos >= 0 && mPlaylist != null && mNextPlayPos < mPlaylist.size()) {
  1149. final long id = mPlaylist.get(mNextPlayPos).mId;
  1150. mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
  1151. } else {
  1152. mPlayer.setNextDataSource(null);
  1153. }
  1154. }
  1155. /**
  1156. * Creates a shuffled playlist used for party mode
  1157. */
  1158. private boolean makeAutoShuffleList() {
  1159. Cursor cursor = null;
  1160. try {
  1161. cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  1162. new String[] {
  1163. MediaStore.Audio.Media._ID
  1164. }, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
  1165. if (cursor == null || cursor.getCount() == 0) {
  1166. return false;
  1167. }
  1168. final int len = cursor.getCount();
  1169. final long[] list = new long[len];
  1170. for (int i = 0; i < len; i++) {
  1171. cursor.moveToNext();
  1172. list[i] = cursor.getLong(0);
  1173. }
  1174. mAutoShuffleList = list;
  1175. return true;
  1176. } catch (final RuntimeException e) {
  1177. } finally {
  1178. if (cursor != null) {
  1179. cursor.close();
  1180. cursor = null;
  1181. }
  1182. }
  1183. return false;
  1184. }
  1185. /**
  1186. * Creates the party shuffle playlist
  1187. */
  1188. private void doAutoShuffleUpdate() {
  1189. boolean notify = false;
  1190. if (mPlayPos > 10) {
  1191. removeTracks(0, mPlayPos - 9);
  1192. notify = true;
  1193. }
  1194. final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));
  1195. for (int i = 0; i < toAdd; i++) {
  1196. int lookback = mHistory.size();
  1197. int idx = -1;
  1198. while (true) {
  1199. idx = mShuffler.nextInt(mAutoShuffleList.length);
  1200. if (!wasRecentlyUsed(idx, lookback)) {
  1201. break;
  1202. }
  1203. lookback /= 2;
  1204. }
  1205. mHistory.add(idx);
  1206. if (mHistory.size() > MAX_HISTORY_SIZE) {
  1207. mHistory.remove(0);
  1208. }
  1209. mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, IdType.NA, -1));
  1210. notify = true;
  1211. }
  1212. if (notify) {
  1213. notifyChange(QUEUE_CHANGED);
  1214. }
  1215. }
  1216. /**/
  1217. private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
  1218. if (lookbacksize == 0) {
  1219. return false;
  1220. }
  1221. final int histsize = mHistory.size();
  1222. if (histsize < lookbacksize) {
  1223. lookbacksize = histsize;
  1224. }
  1225. final int maxidx = histsize - 1;
  1226. for (int i = 0; i < lookbacksize; i++) {
  1227. final long entry = mHistory.get(maxidx - i);
  1228. if (entry == idx) {
  1229. return true;
  1230. }
  1231. }
  1232. return false;
  1233. }
  1234. /**
  1235. * Notify the change-receivers that something has changed.
  1236. */
  1237. private void notifyChange(final String what) {
  1238. if (D) Log.d(TAG, "notifyChange: what = " + what);
  1239. // Update the lockscreen controls
  1240. updateMediaSession(what);
  1241. if (what.equals(POSITION_CHANGED)) {
  1242. return;
  1243. }
  1244. final Intent intent = new Intent(what);
  1245. intent.putExtra("id", getAudioId());
  1246. intent.putExtra("artist", getArtistName());
  1247. intent.putExtra("album", getAlbumName());
  1248. intent.putExtra("track", getTrackName());
  1249. intent.putExtra("playing", isPlaying());
  1250. if (NEW_LYRICS.equals(what)) {
  1251. intent.putExtra("lyrics", mLyrics);
  1252. }
  1253. sendStickyBroadcast(intent);
  1254. final Intent musicIntent = new Intent(intent);
  1255. musicIntent.setAction(what.replace(ELEVEN_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
  1256. sendStickyBroadcast(musicIntent);
  1257. if (what.equals(META_CHANGED)) {
  1258. // Add the track to the recently played list.
  1259. mRecentsCache.addSongId(getAudioId());
  1260. mSongPlayCountCache.bumpSongCount(getAudioId());
  1261. } else if (what.equals(QUEUE_CHANGED)) {
  1262. saveQueue(true);
  1263. if (isPlaying()) {
  1264. // if we are in shuffle mode and our next track is still valid,
  1265. // try to re-use the track
  1266. // We need to reimplement the queue to prevent hacky solutions like this
  1267. // https://cyanogen.atlassian.net/browse/MUSIC-175
  1268. // https://cyanogen.atlassian.net/browse/MUSIC-44
  1269. if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size()
  1270. && getShuffleMode() != SHUFFLE_NONE) {
  1271. setNextTrack(mNextPlayPos);
  1272. } else {
  1273. setNextTrack();
  1274. }
  1275. }
  1276. } else {
  1277. saveQueue(false);
  1278. }
  1279. if (what.equals(PLAYSTATE_CHANGED)) {
  1280. updateNotification();
  1281. }
  1282. // Update the app-widgets
  1283. mAppWidgetSmall.notifyChange(this, what);
  1284. mAppWidgetLarge.notifyChange(this, what);
  1285. mAppWidgetLargeAlternate.notifyChange(this, what);
  1286. }
  1287. private void updateMediaSession(final String what) {
  1288. int playState = mIsSupposedToBePlaying
  1289. ? PlaybackState.STATE_PLAYING
  1290. : PlaybackState.STATE_PAUSED;
  1291. if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
  1292. mSession.setPlaybackState(new PlaybackState.Builder()
  1293. .setState(playState, position(), 1.0f).build());
  1294. } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
  1295. Bitmap albumArt = getAlbumArt(false).getBitmap();
  1296. if (albumArt != null) {
  1297. // RemoteControlClient wants to recycle the bitmaps thrown at it, so we need
  1298. // to make sure not to hand out our cache copy
  1299. Bitmap.Config config = albumArt.getConfig();
  1300. if (config == null) {
  1301. config