PageRenderTime 40ms CodeModel.GetById 4ms RepoModel.GetById 27ms app.codeStats 1ms

/src/com/android/music/MediaPlaybackService.java

https://github.com/zyiro/android_packages_apps_Music
Java | 2086 lines | 1606 code | 167 blank | 313 comment | 398 complexity | 34852582922e8f55f7b3d20960a7b5f9 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  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. package com.android.music;
  17. import android.app.Notification;
  18. import android.app.PendingIntent;
  19. import android.app.Service;
  20. import android.appwidget.AppWidgetManager;
  21. import android.content.ComponentName;
  22. import android.content.ContentResolver;
  23. import android.content.ContentUris;
  24. import android.content.ContentValues;
  25. import android.content.Context;
  26. import android.content.Intent;
  27. import android.content.IntentFilter;
  28. import android.content.BroadcastReceiver;
  29. import android.content.SharedPreferences;
  30. import android.content.SharedPreferences.Editor;
  31. import android.database.Cursor;
  32. import android.database.sqlite.SQLiteException;
  33. import android.media.audiofx.AudioEffect;
  34. import android.media.AudioManager;
  35. import android.media.AudioManager.OnAudioFocusChangeListener;
  36. import android.media.MediaPlayer;
  37. import android.net.Uri;
  38. import android.os.Handler;
  39. import android.os.IBinder;
  40. import android.os.Message;
  41. import android.os.PowerManager;
  42. import android.os.SystemClock;
  43. import android.os.PowerManager.WakeLock;
  44. import android.provider.MediaStore;
  45. import android.telephony.PhoneStateListener;
  46. import android.telephony.TelephonyManager;
  47. import android.util.Log;
  48. import android.widget.RemoteViews;
  49. import android.widget.Toast;
  50. import java.io.FileDescriptor;
  51. import java.io.IOException;
  52. import java.io.PrintWriter;
  53. import java.lang.ref.WeakReference;
  54. import java.util.Random;
  55. import java.util.Vector;
  56. /**
  57. * Provides "background" audio playback capabilities, allowing the
  58. * user to switch between activities without stopping playback.
  59. */
  60. public class MediaPlaybackService extends Service {
  61. /** used to specify whether enqueue() should start playing
  62. * the new list of files right away, next or once all the currently
  63. * queued files have been played
  64. */
  65. public static final int NOW = 1;
  66. public static final int NEXT = 2;
  67. public static final int LAST = 3;
  68. public static final int PLAYBACKSERVICE_STATUS = 1;
  69. public static final int SHUFFLE_NONE = 0;
  70. public static final int SHUFFLE_NORMAL = 1;
  71. public static final int SHUFFLE_AUTO = 2;
  72. public static final int REPEAT_NONE = 0;
  73. public static final int REPEAT_CURRENT = 1;
  74. public static final int REPEAT_ALL = 2;
  75. public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
  76. public static final String META_CHANGED = "com.android.music.metachanged";
  77. public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
  78. public static final String REPEATMODE_CHANGED = "com.android.music.repeatmodechanged";
  79. public static final String SHUFFLEMODE_CHANGED = "com.android.music.shufflemodechanged";
  80. public static final String SERVICECMD = "com.android.music.musicservicecommand";
  81. public static final String CMDNAME = "command";
  82. public static final String CMDTOGGLEPAUSE = "togglepause";
  83. public static final String CMDSTOP = "stop";
  84. public static final String CMDPAUSE = "pause";
  85. public static final String CMDPREVIOUS = "previous";
  86. public static final String CMDNEXT = "next";
  87. public static final String CMDCYCLEREPEAT = "cyclerepeat";
  88. public static final String CMDTOGGLESHUFFLE = "toggleshuffle";
  89. public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
  90. public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
  91. public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
  92. public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
  93. public static final String CYCLEREPEAT_ACTION = "com.android.music.musicservicecommand.cyclerepeat";
  94. public static final String TOGGLESHUFFLE_ACTION = "com.android.music.musicservicecommand.toggleshuffle";
  95. private static final int TRACK_ENDED = 1;
  96. private static final int RELEASE_WAKELOCK = 2;
  97. private static final int SERVER_DIED = 3;
  98. private static final int FADEIN = 4;
  99. private static final int MAX_HISTORY_SIZE = 100;
  100. private MultiPlayer mPlayer;
  101. private String mFileToPlay;
  102. private int mShuffleMode = SHUFFLE_NONE;
  103. private int mRepeatMode = REPEAT_NONE;
  104. private int mMediaMountedCount = 0;
  105. private long [] mAutoShuffleList = null;
  106. private long [] mPlayList = null;
  107. private int mPlayListLen = 0;
  108. private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
  109. private Cursor mCursor;
  110. private int mPlayPos = -1;
  111. private static final String LOGTAG = "MediaPlaybackService";
  112. private final Shuffler mRand = new Shuffler();
  113. private int mOpenFailedCounter = 0;
  114. String[] mCursorCols = new String[] {
  115. "audio._id AS _id", // index must match IDCOLIDX below
  116. MediaStore.Audio.Media.ARTIST,
  117. MediaStore.Audio.Media.ALBUM,
  118. MediaStore.Audio.Media.TITLE,
  119. MediaStore.Audio.Media.DATA,
  120. MediaStore.Audio.Media.MIME_TYPE,
  121. MediaStore.Audio.Media.ALBUM_ID,
  122. MediaStore.Audio.Media.ARTIST_ID,
  123. MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
  124. MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
  125. };
  126. private final static int IDCOLIDX = 0;
  127. private final static int PODCASTCOLIDX = 8;
  128. private final static int BOOKMARKCOLIDX = 9;
  129. private BroadcastReceiver mUnmountReceiver = null;
  130. private WakeLock mWakeLock;
  131. private int mServiceStartId = -1;
  132. private boolean mServiceInUse = false;
  133. private boolean mIsSupposedToBePlaying = false;
  134. private boolean mQuietMode = false;
  135. private AudioManager mAudioManager;
  136. private boolean mQueueIsSaveable = true;
  137. // used to track what type of audio focus loss caused the playback to pause
  138. private boolean mPausedByTransientLossOfFocus = false;
  139. // used to track current volume
  140. private float mCurrentVolume = 1.0f;
  141. private SharedPreferences mPreferences;
  142. // We use this to distinguish between different cards when saving/restoring playlists.
  143. // This will have to change if we want to support multiple simultaneous cards.
  144. private int mCardId;
  145. private MediaAppWidgetProvider4x1 mAppWidgetProvider4x1 = MediaAppWidgetProvider4x1.getInstance();
  146. private MediaAppWidgetProvider4x2 mAppWidgetProvider4x2 = MediaAppWidgetProvider4x2.getInstance();
  147. // interval after which we stop the service when idle
  148. private static final int IDLE_DELAY = 60000;
  149. private boolean mStartPlayback = false;
  150. private void startAndFadeIn() {
  151. mStartPlayback = true;
  152. mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
  153. }
  154. private Handler mMediaplayerHandler = new Handler() {
  155. @Override
  156. public void handleMessage(Message msg) {
  157. MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
  158. switch (msg.what) {
  159. case FADEIN:
  160. if (!isPlaying() && mStartPlayback) {
  161. mStartPlayback = false;
  162. mPlayer.setVolume(0f);
  163. play();
  164. mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
  165. } else {
  166. mCurrentVolume += 0.01f;
  167. if (mCurrentVolume < 1.0f) {
  168. mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
  169. } else {
  170. mCurrentVolume = 1.0f;
  171. }
  172. mPlayer.setVolume(mCurrentVolume);
  173. }
  174. break;
  175. case SERVER_DIED:
  176. if (mIsSupposedToBePlaying) {
  177. next(true);
  178. } else {
  179. // the server died when we were idle, so just
  180. // reopen the same song (it will start again
  181. // from the beginning though when the user
  182. // restarts)
  183. openCurrent();
  184. }
  185. break;
  186. case TRACK_ENDED:
  187. if (mRepeatMode == REPEAT_CURRENT) {
  188. seek(0);
  189. play();
  190. } else {
  191. next(false);
  192. }
  193. break;
  194. case RELEASE_WAKELOCK:
  195. mWakeLock.release();
  196. break;
  197. default:
  198. break;
  199. }
  200. }
  201. };
  202. private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
  203. @Override
  204. public void onReceive(Context context, Intent intent) {
  205. String action = intent.getAction();
  206. String cmd = intent.getStringExtra("command");
  207. MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
  208. if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
  209. next(true);
  210. } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
  211. prev();
  212. } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
  213. if (isPlaying()) {
  214. pause();
  215. mPausedByTransientLossOfFocus = false;
  216. } else {
  217. play();
  218. }
  219. } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
  220. pause();
  221. mPausedByTransientLossOfFocus = false;
  222. } else if (CMDSTOP.equals(cmd)) {
  223. pause();
  224. mPausedByTransientLossOfFocus = false;
  225. seek(0);
  226. } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {
  227. cycleRepeat();
  228. } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {
  229. toggleShuffle();
  230. } else if (MediaAppWidgetProvider4x1.CMDAPPWIDGETUPDATE.equals(cmd)) {
  231. // Someone asked us to refresh a set of specific widgets, probably
  232. // because they were just added.
  233. int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
  234. mAppWidgetProvider4x1.performUpdate(MediaPlaybackService.this, appWidgetIds);
  235. } else if (MediaAppWidgetProvider4x2.CMDAPPWIDGETUPDATE.equals(cmd)) {
  236. // Someone asked us to refresh a set of specific widgets, probably
  237. // because they were just added.
  238. int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
  239. mAppWidgetProvider4x2.performUpdate(MediaPlaybackService.this, appWidgetIds);
  240. }
  241. }
  242. };
  243. private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
  244. public void onAudioFocusChange(int focusChange) {
  245. // AudioFocus is a new feature: focus updates are made verbose on purpose
  246. switch (focusChange) {
  247. case AudioManager.AUDIOFOCUS_LOSS:
  248. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
  249. if (isPlaying()) {
  250. mPausedByTransientLossOfFocus = false;
  251. pause();
  252. }
  253. break;
  254. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
  255. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
  256. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
  257. if (isPlaying()) {
  258. SharedPreferences preferences = getSharedPreferences(MusicSettingsActivity.
  259. PREFERENCES_FILE, MODE_PRIVATE);
  260. if (preferences.getBoolean(MusicSettingsActivity.KEY_ENABLE_FOCUS_LOSS_DUCKING,
  261. false)) {
  262. int duckAttenuationdB = Integer.valueOf(preferences.getString(
  263. MusicSettingsActivity.KEY_DUCK_ATTENUATION_DB,
  264. MusicSettingsActivity.DEFAULT_DUCK_ATTENUATION_DB));
  265. //Convert from decibels to volume level
  266. float duckVolume = (float) Math.pow(10.0, -duckAttenuationdB / 20.0);
  267. Log.v(LOGTAG, "New attentuated volume: " + duckVolume);
  268. mPlayer.setVolume(duckVolume);
  269. } else {
  270. mPausedByTransientLossOfFocus = true;
  271. pause();
  272. }
  273. }
  274. break;
  275. case AudioManager.AUDIOFOCUS_GAIN:
  276. Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
  277. if (isPlaying()) {
  278. mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN,10);
  279. } else if (mPausedByTransientLossOfFocus) {
  280. mPausedByTransientLossOfFocus = false;
  281. startAndFadeIn();
  282. }
  283. break;
  284. default:
  285. Log.e(LOGTAG, "Unknown audio focus change code");
  286. }
  287. }
  288. };
  289. private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
  290. public void onCallStateChanged(int state, String incomingNumber) {
  291. switch (state) {
  292. case TelephonyManager.CALL_STATE_RINGING:
  293. Log.v(LOGTAG, "PhoneState: received CALL_STATE_RINGING");
  294. if (isPlaying()) {
  295. mPausedByTransientLossOfFocus = true;
  296. pause();
  297. }
  298. break;
  299. case TelephonyManager.CALL_STATE_OFFHOOK:
  300. Log.v(LOGTAG, "PhoneState: received CALL_STATE_OFFHOOK");
  301. mPausedByTransientLossOfFocus = false;
  302. if (isPlaying()) {
  303. pause();
  304. }
  305. break;
  306. }
  307. }
  308. };
  309. public MediaPlaybackService() {
  310. }
  311. @Override
  312. public void onCreate() {
  313. super.onCreate();
  314. mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  315. mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
  316. MediaButtonIntentReceiver.class.getName()));
  317. mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
  318. mCardId = MusicUtils.getCardId(this);
  319. registerExternalStorageListener();
  320. // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
  321. mPlayer = new MultiPlayer();
  322. mPlayer.setHandler(mMediaplayerHandler);
  323. reloadQueue();
  324. IntentFilter commandFilter = new IntentFilter();
  325. commandFilter.addAction(SERVICECMD);
  326. commandFilter.addAction(TOGGLEPAUSE_ACTION);
  327. commandFilter.addAction(PAUSE_ACTION);
  328. commandFilter.addAction(NEXT_ACTION);
  329. commandFilter.addAction(PREVIOUS_ACTION);
  330. commandFilter.addAction(CYCLEREPEAT_ACTION);
  331. commandFilter.addAction(TOGGLESHUFFLE_ACTION);
  332. registerReceiver(mIntentReceiver, commandFilter);
  333. PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
  334. mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
  335. mWakeLock.setReferenceCounted(false);
  336. // If the service was idle, but got killed before it stopped itself, the
  337. // system will relaunch it. Make sure it gets stopped again in that case.
  338. Message msg = mDelayedStopHandler.obtainMessage();
  339. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  340. }
  341. @Override
  342. public void onDestroy() {
  343. // Check that we're not being destroyed while something is still playing.
  344. if (isPlaying()) {
  345. Log.e(LOGTAG, "Service being destroyed while still playing.");
  346. }
  347. // release all MediaPlayer resources, including the native player and wakelocks
  348. Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
  349. i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
  350. i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
  351. sendBroadcast(i);
  352. mPlayer.release();
  353. mPlayer = null;
  354. mAudioManager.abandonAudioFocus(mAudioFocusListener);
  355. TelephonyManager telephonyManager =
  356. (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
  357. telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
  358. // make sure there aren't any other messages coming
  359. mDelayedStopHandler.removeCallbacksAndMessages(null);
  360. mMediaplayerHandler.removeCallbacksAndMessages(null);
  361. if (mCursor != null) {
  362. mCursor.close();
  363. mCursor = null;
  364. }
  365. unregisterReceiver(mIntentReceiver);
  366. if (mUnmountReceiver != null) {
  367. unregisterReceiver(mUnmountReceiver);
  368. mUnmountReceiver = null;
  369. }
  370. mWakeLock.release();
  371. super.onDestroy();
  372. }
  373. private final char hexdigits [] = new char [] {
  374. '0', '1', '2', '3',
  375. '4', '5', '6', '7',
  376. '8', '9', 'a', 'b',
  377. 'c', 'd', 'e', 'f'
  378. };
  379. private void saveQueue(boolean full) {
  380. if (!mQueueIsSaveable) {
  381. return;
  382. }
  383. Editor ed = mPreferences.edit();
  384. //long start = System.currentTimeMillis();
  385. if (full) {
  386. StringBuilder q = new StringBuilder();
  387. // The current playlist is saved as a list of "reverse hexadecimal"
  388. // numbers, which we can generate faster than normal decimal or
  389. // hexadecimal numbers, which in turn allows us to save the playlist
  390. // more often without worrying too much about performance.
  391. // (saving the full state takes about 40 ms under no-load conditions
  392. // on the phone)
  393. int len = mPlayListLen;
  394. for (int i = 0; i < len; i++) {
  395. long n = mPlayList[i];
  396. if (n < 0) {
  397. continue;
  398. } else if (n == 0) {
  399. q.append("0;");
  400. } else {
  401. while (n != 0) {
  402. int digit = (int)(n & 0xf);
  403. n >>>= 4;
  404. q.append(hexdigits[digit]);
  405. }
  406. q.append(";");
  407. }
  408. }
  409. //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
  410. ed.putString("queue", q.toString());
  411. ed.putInt("cardid", mCardId);
  412. if (mShuffleMode != SHUFFLE_NONE) {
  413. // In shuffle mode we need to save the history too
  414. len = mHistory.size();
  415. q.setLength(0);
  416. for (int i = 0; i < len; i++) {
  417. int n = mHistory.get(i);
  418. if (n == 0) {
  419. q.append("0;");
  420. } else {
  421. while (n != 0) {
  422. int digit = (n & 0xf);
  423. n >>>= 4;
  424. q.append(hexdigits[digit]);
  425. }
  426. q.append(";");
  427. }
  428. }
  429. ed.putString("history", q.toString());
  430. }
  431. }
  432. ed.putInt("curpos", mPlayPos);
  433. if (mPlayer.isInitialized()) {
  434. ed.putLong("seekpos", mPlayer.position());
  435. }
  436. ed.putInt("repeatmode", mRepeatMode);
  437. ed.putInt("shufflemode", mShuffleMode);
  438. SharedPreferencesCompat.apply(ed);
  439. //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
  440. }
  441. private void reloadQueue() {
  442. String q = null;
  443. int id = mCardId;
  444. if (mPreferences.contains("cardid")) {
  445. id = mPreferences.getInt("cardid", ~mCardId);
  446. }
  447. if (id == mCardId) {
  448. // Only restore the saved playlist if the card is still
  449. // the same one as when the playlist was saved
  450. q = mPreferences.getString("queue", "");
  451. }
  452. int qlen = q != null ? q.length() : 0;
  453. if (qlen > 1) {
  454. //Log.i("@@@@ service", "loaded queue: " + q);
  455. int plen = 0;
  456. int n = 0;
  457. int shift = 0;
  458. for (int i = 0; i < qlen; i++) {
  459. char c = q.charAt(i);
  460. if (c == ';') {
  461. ensurePlayListCapacity(plen + 1);
  462. mPlayList[plen] = n;
  463. plen++;
  464. n = 0;
  465. shift = 0;
  466. } else {
  467. if (c >= '0' && c <= '9') {
  468. n += ((c - '0') << shift);
  469. } else if (c >= 'a' && c <= 'f') {
  470. n += ((10 + c - 'a') << shift);
  471. } else {
  472. // bogus playlist data
  473. plen = 0;
  474. break;
  475. }
  476. shift += 4;
  477. }
  478. }
  479. mPlayListLen = plen;
  480. int pos = mPreferences.getInt("curpos", 0);
  481. if (pos < 0 || pos >= mPlayListLen) {
  482. // The saved playlist is bogus, discard it
  483. mPlayListLen = 0;
  484. return;
  485. }
  486. mPlayPos = pos;
  487. // When reloadQueue is called in response to a card-insertion,
  488. // we might not be able to query the media provider right away.
  489. // To deal with this, try querying for the current file, and if
  490. // that fails, wait a while and try again. If that too fails,
  491. // assume there is a problem and don't restore the state.
  492. Cursor crsr = MusicUtils.query(this,
  493. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  494. new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
  495. if (crsr == null || crsr.getCount() == 0) {
  496. // wait a bit and try again
  497. SystemClock.sleep(3000);
  498. crsr = getContentResolver().query(
  499. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  500. mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
  501. }
  502. if (crsr != null) {
  503. crsr.close();
  504. }
  505. // Make sure we don't auto-skip to the next song, since that
  506. // also starts playback. What could happen in that case is:
  507. // - music is paused
  508. // - go to UMS and delete some files, including the currently playing one
  509. // - come back from UMS
  510. // (time passes)
  511. // - music app is killed for some reason (out of memory)
  512. // - music service is restarted, service restores state, doesn't find
  513. // the "current" file, goes to the next and: playback starts on its
  514. // own, potentially at some random inconvenient time.
  515. mOpenFailedCounter = 20;
  516. mQuietMode = true;
  517. openCurrent();
  518. mQuietMode = false;
  519. if (!mPlayer.isInitialized()) {
  520. // couldn't restore the saved state
  521. mPlayListLen = 0;
  522. return;
  523. }
  524. long seekpos = mPreferences.getLong("seekpos", 0);
  525. seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
  526. Log.d(LOGTAG, "restored queue, currently at position "
  527. + position() + "/" + duration()
  528. + " (requested " + seekpos + ")");
  529. int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
  530. if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
  531. repmode = REPEAT_NONE;
  532. }
  533. mRepeatMode = repmode;
  534. int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
  535. if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
  536. shufmode = SHUFFLE_NONE;
  537. }
  538. if (shufmode != SHUFFLE_NONE) {
  539. // in shuffle mode we need to restore the history too
  540. q = mPreferences.getString("history", "");
  541. qlen = q != null ? q.length() : 0;
  542. if (qlen > 1) {
  543. plen = 0;
  544. n = 0;
  545. shift = 0;
  546. mHistory.clear();
  547. for (int i = 0; i < qlen; i++) {
  548. char c = q.charAt(i);
  549. if (c == ';') {
  550. if (n >= mPlayListLen) {
  551. // bogus history data
  552. mHistory.clear();
  553. break;
  554. }
  555. mHistory.add(n);
  556. n = 0;
  557. shift = 0;
  558. } else {
  559. if (c >= '0' && c <= '9') {
  560. n += ((c - '0') << shift);
  561. } else if (c >= 'a' && c <= 'f') {
  562. n += ((10 + c - 'a') << shift);
  563. } else {
  564. // bogus history data
  565. mHistory.clear();
  566. break;
  567. }
  568. shift += 4;
  569. }
  570. }
  571. }
  572. }
  573. if (shufmode == SHUFFLE_AUTO) {
  574. if (! makeAutoShuffleList()) {
  575. shufmode = SHUFFLE_NONE;
  576. }
  577. }
  578. mShuffleMode = shufmode;
  579. }
  580. }
  581. @Override
  582. public IBinder onBind(Intent intent) {
  583. mDelayedStopHandler.removeCallbacksAndMessages(null);
  584. mServiceInUse = true;
  585. return mBinder;
  586. }
  587. @Override
  588. public void onRebind(Intent intent) {
  589. mDelayedStopHandler.removeCallbacksAndMessages(null);
  590. mServiceInUse = true;
  591. }
  592. @Override
  593. public int onStartCommand(Intent intent, int flags, int startId) {
  594. mServiceStartId = startId;
  595. mDelayedStopHandler.removeCallbacksAndMessages(null);
  596. if (intent != null) {
  597. String action = intent.getAction();
  598. String cmd = intent.getStringExtra("command");
  599. MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
  600. if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
  601. next(true);
  602. } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
  603. if (position() < 2000) {
  604. prev();
  605. } else {
  606. seek(0);
  607. play();
  608. }
  609. } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
  610. if (isPlaying()) {
  611. pause();
  612. mPausedByTransientLossOfFocus = false;
  613. } else {
  614. play();
  615. }
  616. } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
  617. pause();
  618. mPausedByTransientLossOfFocus = false;
  619. } else if (CMDSTOP.equals(cmd)) {
  620. pause();
  621. mPausedByTransientLossOfFocus = false;
  622. seek(0);
  623. } else if (CMDCYCLEREPEAT.equals(cmd) || CYCLEREPEAT_ACTION.equals(action)) {
  624. cycleRepeat();
  625. } else if (CMDTOGGLESHUFFLE.equals(cmd) || TOGGLESHUFFLE_ACTION.equals(action)) {
  626. toggleShuffle();
  627. }
  628. }
  629. // make sure the service will shut down on its own if it was
  630. // just started but not bound to and nothing is playing
  631. mDelayedStopHandler.removeCallbacksAndMessages(null);
  632. Message msg = mDelayedStopHandler.obtainMessage();
  633. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  634. return START_STICKY;
  635. }
  636. @Override
  637. public boolean onUnbind(Intent intent) {
  638. mServiceInUse = false;
  639. // Take a snapshot of the current playlist
  640. saveQueue(true);
  641. if (isPlaying() || mPausedByTransientLossOfFocus) {
  642. // something is currently playing, or will be playing once
  643. // an in-progress action requesting audio focus ends, so don't stop the service now.
  644. return true;
  645. }
  646. // If there is a playlist but playback is paused, then wait a while
  647. // before stopping the service, so that pause/resume isn't slow.
  648. // Also delay stopping the service if we're transitioning between tracks.
  649. if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
  650. Message msg = mDelayedStopHandler.obtainMessage();
  651. mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
  652. return true;
  653. }
  654. // No active playlist, OK to stop the service right now
  655. stopSelf(mServiceStartId);
  656. return true;
  657. }
  658. private Handler mDelayedStopHandler = new Handler() {
  659. @Override
  660. public void handleMessage(Message msg) {
  661. // Check again to make sure nothing is playing right now
  662. if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
  663. || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
  664. return;
  665. }
  666. // save the queue again, because it might have changed
  667. // since the user exited the music app (because of
  668. // party-shuffle or because the play-position changed)
  669. saveQueue(true);
  670. stopSelf(mServiceStartId);
  671. }
  672. };
  673. /**
  674. * Called when we receive a ACTION_MEDIA_EJECT notification.
  675. *
  676. * @param storagePath path to mount point for the removed media
  677. */
  678. public void closeExternalStorageFiles(String storagePath) {
  679. // stop playback and clean up if the SD card is going to be unmounted.
  680. stop(true);
  681. notifyChange(QUEUE_CHANGED);
  682. notifyChange(META_CHANGED);
  683. }
  684. /**
  685. * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
  686. * The intent will call closeExternalStorageFiles() if the external media
  687. * is going to be ejected, so applications can clean up any files they have open.
  688. */
  689. public void registerExternalStorageListener() {
  690. if (mUnmountReceiver == null) {
  691. mUnmountReceiver = new BroadcastReceiver() {
  692. @Override
  693. public void onReceive(Context context, Intent intent) {
  694. String action = intent.getAction();
  695. if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
  696. saveQueue(true);
  697. mQueueIsSaveable = false;
  698. closeExternalStorageFiles(intent.getData().getPath());
  699. } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
  700. mMediaMountedCount++;
  701. mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
  702. reloadQueue();
  703. mQueueIsSaveable = true;
  704. notifyChange(QUEUE_CHANGED);
  705. notifyChange(META_CHANGED);
  706. }
  707. }
  708. };
  709. IntentFilter iFilter = new IntentFilter();
  710. iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
  711. iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
  712. iFilter.addDataScheme("file");
  713. registerReceiver(mUnmountReceiver, iFilter);
  714. }
  715. }
  716. /**
  717. * Notify the change-receivers that something has changed.
  718. * The intent that is sent contains the following data
  719. * for the currently playing track:
  720. * "id" - Integer: the database row ID
  721. * "artist" - String: the name of the artist
  722. * "album" - String: the name of the album
  723. * "track" - String: the name of the track
  724. * The intent has an action that is one of
  725. * "com.android.music.metachanged"
  726. * "com.android.music.queuechanged",
  727. * "com.android.music.playbackcomplete"
  728. * "com.android.music.playstatechanged"
  729. * respectively indicating that a new track has
  730. * started playing, that the playback queue has
  731. * changed, that playback has stopped because
  732. * the last file in the list has been played,
  733. * or that the play-state changed (paused/resumed).
  734. */
  735. private void notifyChange(String what) {
  736. Intent i = new Intent(what);
  737. i.putExtra("id", Long.valueOf(getAudioId()));
  738. i.putExtra("artist", getArtistName());
  739. i.putExtra("album",getAlbumName());
  740. i.putExtra("track", getTrackName());
  741. i.putExtra("playing", isPlaying());
  742. i.putExtra("songid", getAudioId());
  743. i.putExtra("albumid", getAlbumId());
  744. sendStickyBroadcast(i);
  745. if (what.equals(QUEUE_CHANGED)) {
  746. saveQueue(true);
  747. } else {
  748. saveQueue(false);
  749. }
  750. // Share this notification directly with our widgets
  751. mAppWidgetProvider4x1.notifyChange(this, what);
  752. mAppWidgetProvider4x2.notifyChange(this, what);
  753. }
  754. private void ensurePlayListCapacity(int size) {
  755. if (mPlayList == null || size > mPlayList.length) {
  756. // reallocate at 2x requested size so we don't
  757. // need to grow and copy the array for every
  758. // insert
  759. long [] newlist = new long[size * 2];
  760. int len = mPlayList != null ? mPlayList.length : mPlayListLen;
  761. for (int i = 0; i < len; i++) {
  762. newlist[i] = mPlayList[i];
  763. }
  764. mPlayList = newlist;
  765. }
  766. // FIXME: shrink the array when the needed size is much smaller
  767. // than the allocated size
  768. }
  769. // insert the list of songs at the specified position in the playlist
  770. private void addToPlayList(long [] list, int position) {
  771. int addlen = list.length;
  772. if (position < 0) { // overwrite
  773. mPlayListLen = 0;
  774. position = 0;
  775. }
  776. ensurePlayListCapacity(mPlayListLen + addlen);
  777. if (position > mPlayListLen) {
  778. position = mPlayListLen;
  779. }
  780. // move part of list after insertion point
  781. int tailsize = mPlayListLen - position;
  782. for (int i = tailsize ; i > 0 ; i--) {
  783. mPlayList[position + i] = mPlayList[position + i - addlen];
  784. }
  785. // copy list into playlist
  786. for (int i = 0; i < addlen; i++) {
  787. mPlayList[position + i] = list[i];
  788. }
  789. mPlayListLen += addlen;
  790. if (mPlayListLen == 0) {
  791. mCursor.close();
  792. mCursor = null;
  793. notifyChange(META_CHANGED);
  794. }
  795. }
  796. /**
  797. * Appends a list of tracks to the current playlist.
  798. * If nothing is playing currently, playback will be started at
  799. * the first track.
  800. * If the action is NOW, playback will switch to the first of
  801. * the new tracks immediately.
  802. * @param list The list of tracks to append.
  803. * @param action NOW, NEXT or LAST
  804. */
  805. public void enqueue(long [] list, int action) {
  806. synchronized(this) {
  807. if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
  808. addToPlayList(list, mPlayPos + 1);
  809. notifyChange(QUEUE_CHANGED);
  810. } else {
  811. // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
  812. addToPlayList(list, Integer.MAX_VALUE);
  813. notifyChange(QUEUE_CHANGED);
  814. if (action == NOW) {
  815. mPlayPos = mPlayListLen - list.length;
  816. openCurrent();
  817. play();
  818. notifyChange(META_CHANGED);
  819. return;
  820. }
  821. }
  822. if (mPlayPos < 0) {
  823. mPlayPos = 0;
  824. openCurrent();
  825. play();
  826. notifyChange(META_CHANGED);
  827. }
  828. }
  829. }
  830. /**
  831. * Replaces the current playlist with a new list,
  832. * and prepares for starting playback at the specified
  833. * position in the list, or a random position if the
  834. * specified position is 0.
  835. * @param list The new list of tracks.
  836. */
  837. public void open(long [] list, int position) {
  838. synchronized (this) {
  839. if (mShuffleMode == SHUFFLE_AUTO) {
  840. mShuffleMode = SHUFFLE_NORMAL;
  841. }
  842. long oldId = getAudioId();
  843. int listlength = list.length;
  844. boolean newlist = true;
  845. if (mPlayListLen == listlength) {
  846. // possible fast path: list might be the same
  847. newlist = false;
  848. for (int i = 0; i < listlength; i++) {
  849. if (list[i] != mPlayList[i]) {
  850. newlist = true;
  851. break;
  852. }
  853. }
  854. }
  855. if (newlist) {
  856. addToPlayList(list, -1);
  857. notifyChange(QUEUE_CHANGED);
  858. }
  859. if (position >= 0) {
  860. mPlayPos = position;
  861. } else {
  862. mPlayPos = mRand.nextInt(mPlayListLen);
  863. }
  864. mHistory.clear();
  865. saveBookmarkIfNeeded();
  866. openCurrent();
  867. if (oldId != getAudioId()) {
  868. notifyChange(META_CHANGED);
  869. }
  870. }
  871. }
  872. /**
  873. * Moves the item at index1 to index2.
  874. * @param index1
  875. * @param index2
  876. */
  877. public void moveQueueItem(int index1, int index2) {
  878. synchronized (this) {
  879. if (index1 >= mPlayListLen) {
  880. index1 = mPlayListLen - 1;
  881. }
  882. if (index2 >= mPlayListLen) {
  883. index2 = mPlayListLen - 1;
  884. }
  885. if (index1 < index2) {
  886. long tmp = mPlayList[index1];
  887. for (int i = index1; i < index2; i++) {
  888. mPlayList[i] = mPlayList[i+1];
  889. }
  890. mPlayList[index2] = tmp;
  891. if (mPlayPos == index1) {
  892. mPlayPos = index2;
  893. } else if (mPlayPos >= index1 && mPlayPos <= index2) {
  894. mPlayPos--;
  895. }
  896. } else if (index2 < index1) {
  897. long tmp = mPlayList[index1];
  898. for (int i = index1; i > index2; i--) {
  899. mPlayList[i] = mPlayList[i-1];
  900. }
  901. mPlayList[index2] = tmp;
  902. if (mPlayPos == index1) {
  903. mPlayPos = index2;
  904. } else if (mPlayPos >= index2 && mPlayPos <= index1) {
  905. mPlayPos++;
  906. }
  907. }
  908. notifyChange(QUEUE_CHANGED);
  909. }
  910. }
  911. /**
  912. * Returns the current play list
  913. * @return An array of integers containing the IDs of the tracks in the play list
  914. */
  915. public long [] getQueue() {
  916. synchronized (this) {
  917. int len = mPlayListLen;
  918. long [] list = new long[len];
  919. for (int i = 0; i < len; i++) {
  920. list[i] = mPlayList[i];
  921. }
  922. return list;
  923. }
  924. }
  925. private void openCurrent() {
  926. synchronized (this) {
  927. if (mCursor != null) {
  928. mCursor.close();
  929. mCursor = null;
  930. }
  931. if (mPlayListLen == 0) {
  932. return;
  933. }
  934. stop(false);
  935. String id = String.valueOf(mPlayList[mPlayPos]);
  936. mCursor = getContentResolver().query(
  937. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  938. mCursorCols, "_id=" + id , null, null);
  939. if (mCursor != null) {
  940. mCursor.moveToFirst();
  941. open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
  942. // go to bookmark if needed
  943. if (isPodcast()) {
  944. long bookmark = getBookmark();
  945. // Start playing a little bit before the bookmark,
  946. // so it's easier to get back in to the narrative.
  947. seek(bookmark - 5000);
  948. }
  949. }
  950. }
  951. }
  952. /**
  953. * Opens the specified file and readies it for playback.
  954. *
  955. * @param path The full path of the file to be opened.
  956. */
  957. public void open(String path) {
  958. synchronized (this) {
  959. if (path == null) {
  960. return;
  961. }
  962. // if mCursor is null, try to associate path with a database cursor
  963. if (mCursor == null) {
  964. ContentResolver resolver = getContentResolver();
  965. Uri uri;
  966. String where;
  967. String selectionArgs[];
  968. if (path.startsWith("content://media/")) {
  969. uri = Uri.parse(path);
  970. where = null;
  971. selectionArgs = null;
  972. } else {
  973. uri = MediaStore.Audio.Media.getContentUriForPath(path);
  974. where = MediaStore.Audio.Media.DATA + "=?";
  975. selectionArgs = new String[] { path };
  976. }
  977. try {
  978. mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
  979. if (mCursor != null) {
  980. if (mCursor.getCount() == 0) {
  981. mCursor.close();
  982. mCursor = null;
  983. } else {
  984. mCursor.moveToNext();
  985. ensurePlayListCapacity(1);
  986. mPlayListLen = 1;
  987. mPlayList[0] = mCursor.getLong(IDCOLIDX);
  988. mPlayPos = 0;
  989. }
  990. }
  991. } catch (UnsupportedOperationException ex) {
  992. }
  993. }
  994. mFileToPlay = path;
  995. mPlayer.setDataSource(mFileToPlay);
  996. if (! mPlayer.isInitialized()) {
  997. stop(true);
  998. if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
  999. // beware: this ends up being recursive because next() calls open() again.
  1000. next(false);
  1001. }
  1002. if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
  1003. // need to make sure we only shows this once
  1004. mOpenFailedCounter = 0;
  1005. if (!mQuietMode) {
  1006. Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
  1007. }
  1008. Log.d(LOGTAG, "Failed to open file for playback");
  1009. }
  1010. } else {
  1011. mOpenFailedCounter = 0;
  1012. }
  1013. }
  1014. }
  1015. /**
  1016. * Starts playback of a previously opened file.
  1017. */
  1018. public void play() {
  1019. TelephonyManager telephonyManager =
  1020. (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
  1021. if (telephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
  1022. return;
  1023. }
  1024. mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
  1025. AudioManager.AUDIOFOCUS_GAIN);
  1026. mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
  1027. MediaButtonIntentReceiver.class.getName()));
  1028. telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
  1029. if (mPlayer.isInitialized()) {
  1030. // if we are at the end of the song, go to the next song first
  1031. long duration = mPlayer.duration();
  1032. if (mRepeatMode != REPEAT_CURRENT && duration > 2000 &&
  1033. mPlayer.position() >= duration - 2000) {
  1034. next(true);
  1035. }
  1036. mPlayer.start();
  1037. RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
  1038. views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
  1039. if (getAudioId() < 0) {
  1040. // streaming
  1041. views.setTextViewText(R.id.trackname, getPath());
  1042. views.setTextViewText(R.id.artistalbum, null);
  1043. } else {
  1044. String artist = getArtistName();
  1045. views.setTextViewText(R.id.trackname, getTrackName());
  1046. if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
  1047. artist = getString(R.string.unknown_artist_name);
  1048. }
  1049. String album = getAlbumName();
  1050. if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
  1051. album = getString(R.string.unknown_album_name);
  1052. }
  1053. views.setTextViewText(R.id.artistalbum,
  1054. getString(R.string.notification_artist_album, artist, album)
  1055. );
  1056. }
  1057. Notification status = new Notification();
  1058. status.contentView = views;
  1059. status.flags |= Notification.FLAG_ONGOING_EVENT;
  1060. status.icon = R.drawable.stat_notify_musicplayer;
  1061. status.contentIntent = PendingIntent.getActivity(this, 0,
  1062. new Intent("com.android.music.PLAYBACK_VIEWER")
  1063. .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
  1064. startForeground(PLAYBACKSERVICE_STATUS, status);
  1065. if (!mIsSupposedToBePlaying) {
  1066. mIsSupposedToBePlaying = true;
  1067. notifyChange(PLAYSTATE_CHANGED);
  1068. }
  1069. } else if (mPlayListLen <= 0) {
  1070. // This is mostly so that if you press 'play' on a bluetooth headset
  1071. // without every having played anything before, it will still play
  1072. // something.
  1073. setShuffleMode(SHUFFLE_AUTO);
  1074. }
  1075. }
  1076. private void stop(boolean remove_status_icon) {
  1077. if (mPlayer.isInitialized()) {
  1078. mPlayer.stop();
  1079. }
  1080. mFileToPlay = null;
  1081. if (mCursor != null) {
  1082. mCursor.close();
  1083. mCursor = null;
  1084. }
  1085. if (remove_status_icon) {
  1086. gotoIdleState();
  1087. } else {
  1088. stopForeground(false);
  1089. }
  1090. if (remove_status_icon) {
  1091. mIsSupposedToBePlaying = false;
  1092. }
  1093. }
  1094. /**
  1095. * Stops playback.
  1096. */
  1097. public void stop() {
  1098. stop(true);
  1099. }
  1100. /**
  1101. * Pauses playback (call play() to resume)
  1102. */
  1103. public void pause() {
  1104. synchronized(this) {
  1105. if (isPlaying()) {
  1106. mPlayer.pause();
  1107. gotoIdleState();
  1108. mIsSupposedToBePlaying = false;
  1109. notifyChange(PLAYSTATE_CHANGED);
  1110. saveBookmarkIfNeeded();
  1111. }
  1112. }
  1113. }
  1114. /** Returns whether something is currently playing
  1115. *
  1116. * @return true if something is playing (or will be playing shortly, in case
  1117. * we're currently transitioning between tracks), false if not.
  1118. */
  1119. public boolean isPlaying() {
  1120. return mIsSupposedToBePlaying;
  1121. }
  1122. /*
  1123. Desired behavior for prev/next/shuffle:
  1124. - NEXT will move to the next track in the list when not shuffling, and to
  1125. a track randomly picked from the not-yet-played tracks when shuffling.
  1126. If all tracks have already been played, pick from the full set, but
  1127. avoid picking the previously played track if possible.
  1128. - when shuffling, PREV will go to the previously played track. Hitting PREV
  1129. again will go to the track played before that, etc. When the start of the
  1130. history has been reached, PREV is a no-op.
  1131. When not shuffling, PREV will go to the sequentially previous track (the
  1132. difference with the shuffle-case is mainly that when not shuffling, the
  1133. user can back up to tracks that are not in the history).
  1134. Example:
  1135. When playing an album with 10 tracks from the start, and enabling shuffle
  1136. while playing track 5, the remaining tracks (6-10) will be shuffled, e.g.
  1137. the final play order might be 1-2-3-4-5-8-10-6-9-7.
  1138. When hitting 'prev' 8 times while playing track 7 in this example, the
  1139. user will go to tracks 9-6-10-8-5-4-3-2. If the user then hits 'next',
  1140. a random track will be picked again. If at any time user disables shuffling
  1141. the next/previous track will be picked in sequential order again.
  1142. */
  1143. public void prev() {
  1144. synchronized (this) {
  1145. if (mShuffleMode == SHUFFLE_NORMAL) {
  1146. // go to previously-played track and rem…

Large files files are truncated, but you can click here to view the full file