PageRenderTime 113ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

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

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

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