/src/com/android/music/MediaPlaybackService.java
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
- /*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.android.music;
- import android.app.Notification;
- import android.app.PendingIntent;
- import android.app.Service;
- import android.appwidget.AppWidgetManager;
- import android.content.ComponentName;
- import android.content.ContentResolver;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.BroadcastReceiver;
- import android.content.SharedPreferences;
- import android.content.SharedPreferences.Editor;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteException;
- import android.media.AudioManager;
- import android.media.AudioManager.OnAudioFocusChangeListener;
- import android.media.MediaPlayer;
- import android.net.Uri;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.PowerManager;
- import android.os.SystemClock;
- import android.os.PowerManager.WakeLock;
- import android.preference.PreferenceManager;
- import android.provider.MediaStore;
- import android.telephony.PhoneStateListener;
- import android.telephony.TelephonyManager;
- import android.util.Log;
- import android.widget.RemoteViews;
- import android.widget.Toast;
- import java.io.FileDescriptor;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.lang.ref.WeakReference;
- import java.util.Random;
- import java.util.Vector;
- /**
- * Provides "background" audio playback capabilities, allowing the
- * user to switch between activities without stopping playback.
- */
- public class MediaPlaybackService extends Service {
- /** used to specify whether enqueue() should start playing
- * the new list of files right away, next or once all the currently
- * queued files have been played
- */
- public static final int NOW = 1;
- public static final int NEXT = 2;
- public static final int LAST = 3;
- public static final int PLAYBACKSERVICE_STATUS = 1;
- public static final int SHUFFLE_NONE = 0;
- public static final int SHUFFLE_NORMAL = 1;
- public static final int SHUFFLE_AUTO = 2;
- public static final int REPEAT_NONE = 0;
- public static final int REPEAT_CURRENT = 1;
- public static final int REPEAT_ALL = 2;
- public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
- public static final String META_CHANGED = "com.android.music.metachanged";
- public static final String QUEUE_CHANGED = "com.android.music.queuechanged";
- public static final String PLAYBACK_COMPLETE = "com.android.music.playbackcomplete";
- public static final String ASYNC_OPEN_COMPLETE = "com.android.music.asyncopencomplete";
- public static final String REFRESH_PROGRESSBAR = "com.android.music.refreshui";
- public static final String REPEAT_CHANGED = "com.android.music.repeatmodechanged";
- public static final String SHUFFLE_CHANGED = "com.android.music.shufflemodechanged";
- public static final String SERVICECMD = "com.android.music.musicservicecommand";
- public static final String CMDNAME = "command";
- public static final String CMDTOGGLEPAUSE = "togglepause";
- public static final String CMDSTOP = "stop";
- public static final String CMDPAUSE = "pause";
- public static final String CMDPREVIOUS = "previous";
- public static final String CMDNEXT = "next";
- public static final String CMDSHUFFLE = "shuffle";
- public static final String CMDREPEAT = "repeat";
- public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
- public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
- public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
- public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
- public static final String SHUFFLE_ACTION = "com.android.music.musicservicecommand.shuffle";
- public static final String REPEAT_ACTION = "com.android.music.musicservicecommand.repeat";
- private static final int TRACK_ENDED = 1;
- private static final int RELEASE_WAKELOCK = 2;
- private static final int SERVER_DIED = 3;
- private static final int FADEIN = 4;
- private static final int FADEIN_FROM_DUCK = 5;
- private static final int MAX_HISTORY_SIZE = 100;
- private MultiPlayer mPlayer;
- private String mFileToPlay;
- private int mShuffleMode = SHUFFLE_NONE;
- private int mRepeatMode = REPEAT_NONE;
- private int mMediaMountedCount = 0;
- private long [] mAutoShuffleList = null;
- private boolean mOneShot;
- private long [] mPlayList = null;
- private int mPlayListLen = 0;
- private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
- private Cursor mCursor;
- private int mPlayPos = -1;
- private static final String LOGTAG = "MediaPlaybackService";
- private final Shuffler mRand = new Shuffler();
- private int mOpenFailedCounter = 0;
- private static boolean mPlayPrev = false;
- String[] mCursorCols = new String[] {
- "audio._id AS _id", // index must match IDCOLIDX below
- MediaStore.Audio.Media.ARTIST,
- MediaStore.Audio.Media.ALBUM,
- MediaStore.Audio.Media.TITLE,
- MediaStore.Audio.Media.DATA,
- MediaStore.Audio.Media.MIME_TYPE,
- MediaStore.Audio.Media.ALBUM_ID,
- MediaStore.Audio.Media.ARTIST_ID,
- MediaStore.Audio.Media.IS_PODCAST, // index must match PODCASTCOLIDX below
- MediaStore.Audio.Media.BOOKMARK // index must match BOOKMARKCOLIDX below
- };
- private final static int IDCOLIDX = 0;
- private final static int PODCASTCOLIDX = 8;
- private final static int BOOKMARKCOLIDX = 9;
- private BroadcastReceiver mUnmountReceiver = null;
- private WakeLock mWakeLock;
- private int mServiceStartId = -1;
- private boolean mServiceInUse = false;
- private boolean mResumeAfterCall = false;
- private boolean mPausedInCall = false;
- private boolean mIsSupposedToBePlaying = false;
- private boolean mQuietMode = false;
- private AudioManager mAudioManager;
- // used to track what type of audio focus loss caused the playback to pause
- private boolean mPausedByTransientLossOfFocus = false;
- private float mTransientDuckVolume = 1.0f;
- private SharedPreferences mPreferences;
- // We use this to distinguish between different cards when saving/restoring playlists.
- // This will have to change if we want to support multiple simultaneous cards.
- private int mCardId;
- private MediaAppWidgetProvider mAppWidgetProvider = MediaAppWidgetProvider.getInstance();
- private MediaAppWidgetProvider2 mAppWidgetProvider2 = MediaAppWidgetProvider2.getInstance();
- private MediaAppWidgetProvider3 mAppWidgetProvider3 = MediaAppWidgetProvider3.getInstance();
- private MediaAppWidgetProvider4 mAppWidgetProvider4 = MediaAppWidgetProvider4.getInstance();
- private MediaAppWidgetProvider5 mAppWidgetProvider5 = MediaAppWidgetProvider5.getInstance();
- // interval after which we stop the service when idle
- private static final int IDLE_DELAY = 60000;
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (state == TelephonyManager.CALL_STATE_RINGING) {
- AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
- if (ringvolume > 0) {
- mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
- pause();
- if(mResumeAfterCall)
- mPausedInCall=true;
- }
- } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
- // pause the music while a conversation is in progress
- mResumeAfterCall = (isPlaying() || mResumeAfterCall) && (getAudioId() >= 0);
- pause();
- if(mResumeAfterCall)
- mPausedInCall=true;
- } else if (state == TelephonyManager.CALL_STATE_IDLE) {
- // start playing again
- if (mResumeAfterCall) {
- // resume playback only if music was playing
- // when the call was answered
- startAndFadeIn();
- mResumeAfterCall = false;
- mPausedInCall=false;
- }
- }
- }
- };
- private void startAndFadeIn() {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
- }
- private Handler mMediaplayerHandler = new Handler() {
- float mCurrentVolume = 1.0f;
- @Override
- public void handleMessage(Message msg) {
- MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
- switch (msg.what) {
- case FADEIN_FROM_DUCK:
- if (isPlaying()) {
- mCurrentVolume = mTransientDuckVolume;
- mTransientDuckVolume = 1.0f;
- } else {
- mTransientDuckVolume = 1.0f;
- break;
- }
- // Fall through if playing
- case FADEIN:
- if (!isPlaying()) {
- mCurrentVolume = 0f;
- mPlayer.setVolume(mCurrentVolume);
- play();
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
- } else {
- mCurrentVolume += 0.01f;
- if (mCurrentVolume < 1.0f) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
- } else {
- mCurrentVolume = 1.0f;
- }
- mPlayer.setVolume(mCurrentVolume);
- }
- break;
- case SERVER_DIED:
- if (mIsSupposedToBePlaying) {
- next(true);
- } else {
- // the server died when we were idle, so just
- // reopen the same song (it will start again
- // from the beginning though when the user
- // restarts)
- openCurrent();
- }
- break;
- case TRACK_ENDED:
- if (mRepeatMode == REPEAT_CURRENT) {
- seek(0);
- play();
- } else if (!mOneShot) {
- next(false);
- } else {
- notifyChange(PLAYBACK_COMPLETE);
- mIsSupposedToBePlaying = false;
- }
- break;
- case RELEASE_WAKELOCK:
- mWakeLock.release();
- break;
- default:
- break;
- }
- }
- };
- private void toggleShuffle() {
- int shuffle = getShuffleMode();
- if (shuffle == SHUFFLE_NONE) {
- setShuffleMode(SHUFFLE_NORMAL);
- if (getRepeatMode() == REPEAT_CURRENT) {
- setRepeatMode(REPEAT_ALL);
- }
- } else if (shuffle == SHUFFLE_NORMAL ||
- shuffle == SHUFFLE_AUTO) {
- setShuffleMode(SHUFFLE_NONE);
- } else {
- }
- notifyChange(SHUFFLE_CHANGED);
- }
- private void cycleRepeat() {
- int mode = getRepeatMode();
- if (mode == REPEAT_NONE) {
- setRepeatMode(REPEAT_ALL);
- } else if (mode == REPEAT_ALL) {
- setRepeatMode(REPEAT_CURRENT);
- if (getShuffleMode() != SHUFFLE_NONE) {
- setShuffleMode(SHUFFLE_NONE);
- }
- } else {
- setRepeatMode(REPEAT_NONE);
- }
- notifyChange(REPEAT_CHANGED);
- }
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String cmd = intent.getStringExtra("command");
- MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
- if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
- boolean trackballSource = intent.getBooleanExtra("trackball", false);
- // Trackball initiated, but preference not set
- if (trackballSource && !MusicUtils.getBooleanPref(context,
- MusicSettingsActivity.KEY_DOUBLETAP_TRACKBALL_SKIP, false)) {
- // Do nothing
- }
- else
- next(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- prev();
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (isPlaying()) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else {
- play();
- }
- } else if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
- if (MusicUtils.getBooleanPref(context,
- MusicSettingsActivity.KEY_UNPAUSE_ON_HEADSET_PLUG, false)
- && "Headset".equals(intent.getStringExtra("name"))
- && intent.getIntExtra("state", 0) == 1) {
- if (!isPlaying()) {
- Log.d(LOGTAG, "Headset connected, resuming playback");
- play();
- }
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- seek(0);
- } else if (CMDSHUFFLE.equals(cmd) || SHUFFLE_ACTION.equals(action)) {
- toggleShuffle();
- } else if (CMDREPEAT.equals(cmd) || REPEAT_ACTION.equals(action)) {
- cycleRepeat();
- } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
- // Someone asked us to refresh a set of specific widgets, probably
- // because they were just added.
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
- Log.d("MediaAppWidgetProvider1", "MediaAppWidgetProvider1 is recieving");
- } else if (MediaAppWidgetProvider2.CMDAPPWIDGETUPDATE.equals(cmd)) {
- // Someone asked us to refresh a set of specific widgets, probably
- // because they were just added.
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider2.performUpdate(MediaPlaybackService.this, appWidgetIds);
- Log.d("MediaAppWidgetProvider2", "MediaAppWidgetProvider2 is recieving");
- } else if (MediaAppWidgetProvider3.CMDAPPWIDGETUPDATE.equals(cmd)) {
- // Someone asked us to refresh a set of specific widgets, probably
- // because they were just added.
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider3.performUpdate(MediaPlaybackService.this, appWidgetIds);
- Log.d("MediaAppWidgetProvider3", "MediaAppWidgetProvider3 is recieving");
- } else if (MediaAppWidgetProvider4.CMDAPPWIDGETUPDATE.equals(cmd)) {
- // Someone asked us to refresh a set of specific widgets, probably
- // because they were just added.
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider4.performUpdate(MediaPlaybackService.this, appWidgetIds);
- Log.d("MediaAppWidgetProvider4", "MediaAppWidgetProvider4 is recieving");
- } else if (MediaAppWidgetProvider5.CMDAPPWIDGETUPDATE.equals(cmd)) {
- // Someone asked us to refresh a set of specific widgets, probably
- // because they were just added.
- int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- mAppWidgetProvider5.performUpdate(MediaPlaybackService.this, appWidgetIds);
- Log.d("MediaAppWidgetProvider5", "MediaAppWidgetProvider5 is recieving");
- }
- }
- };
- private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
- @Override
- public void onAudioFocusChange(int focusChange) {
- // AudioFocus is a new feature: focus updates are made verbose on purpose
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
- if(isPlaying()) {
- mPausedByTransientLossOfFocus = false;
- pause();
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
- if(isPlaying()) {
- int attenuation = getTransientDuckAttenuation();
- if (attenuation < 0) {
- // Pause
- mPausedByTransientLossOfFocus = true;
- pause();
- } else if (attenuation > 0) {
- // 0 Means no ducking wanted so do nothing in that case
- // setVolume wants a scalar value, not logarithmic
- mTransientDuckVolume = (float) Math.pow(10, -attenuation / 20f);
- mPlayer.setVolume(mTransientDuckVolume);
- }
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
- if(isPlaying()) {
- mPausedByTransientLossOfFocus = true;
- pause();
- }
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
- if(!isPlaying() && mPausedByTransientLossOfFocus) {
- mPausedByTransientLossOfFocus = false;
- startAndFadeIn();
- } else if (isPlaying()) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN_FROM_DUCK, 10);
- }
- break;
- default:
- Log.e(LOGTAG, "Unknown audio focus change code");
- }
- }
- };
- public MediaPlaybackService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),
- MediaButtonIntentReceiver.class.getName()));
- mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
- mCardId = MusicUtils.getCardId(this);
- registerExternalStorageListener();
- // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
- mPlayer = new MultiPlayer();
- mPlayer.setHandler(mMediaplayerHandler);
- reloadQueue();
- IntentFilter commandFilter = new IntentFilter();
- commandFilter.addAction(SERVICECMD);
- commandFilter.addAction(TOGGLEPAUSE_ACTION);
- commandFilter.addAction(PAUSE_ACTION);
- commandFilter.addAction(NEXT_ACTION);
- commandFilter.addAction(PREVIOUS_ACTION);
- commandFilter.addAction(SHUFFLE_ACTION);
- commandFilter.addAction(REPEAT_ACTION);
- commandFilter.addAction(Intent.ACTION_HEADSET_PLUG);
- registerReceiver(mIntentReceiver, commandFilter);
- TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
- mWakeLock.setReferenceCounted(false);
- // If the service was idle, but got killed before it stopped itself, the
- // system will relaunch it. Make sure it gets stopped again in that case.
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- }
- @Override
- public void onDestroy() {
- // Check that we're not being destroyed while something is still playing.
- if (isPlaying()) {
- Log.e(LOGTAG, "Service being destroyed while still playing.");
- }
- // release all MediaPlayer resources, including the native player and wakelocks
- mPlayer.release();
- mPlayer = null;
- mAudioManager.abandonAudioFocus(mAudioFocusListener);
- // make sure there aren't any other messages coming
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mMediaplayerHandler.removeCallbacksAndMessages(null);
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- tmgr.listen(mPhoneStateListener, 0);
- unregisterReceiver(mIntentReceiver);
- if (mUnmountReceiver != null) {
- unregisterReceiver(mUnmountReceiver);
- mUnmountReceiver = null;
- }
- mWakeLock.release();
- super.onDestroy();
- }
- private final char hexdigits [] = new char [] {
- '0', '1', '2', '3',
- '4', '5', '6', '7',
- '8', '9', 'a', 'b',
- 'c', 'd', 'e', 'f'
- };
- private int getTransientDuckAttenuation() {
- return Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(this).
- getString(MusicSettingsActivity.KEY_DUCK_ATTENUATION, "10"));
- }
- private void saveQueue(boolean full) {
- if (mOneShot) {
- return;
- }
- Editor ed = mPreferences.edit();
- //long start = System.currentTimeMillis();
- if (full) {
- StringBuilder q = new StringBuilder();
- // The current playlist is saved as a list of "reverse hexadecimal"
- // numbers, which we can generate faster than normal decimal or
- // hexadecimal numbers, which in turn allows us to save the playlist
- // more often without worrying too much about performance.
- // (saving the full state takes about 40 ms under no-load conditions
- // on the phone)
- int len = mPlayListLen;
- for (int i = 0; i < len; i++) {
- long n = mPlayList[i];
- if (n == 0) {
- q.append("0;");
- } else {
- while (n != 0) {
- int digit = (int)(n & 0xf);
- n >>= 4;
- q.append(hexdigits[digit]);
- }
- q.append(";");
- }
- }
- //Log.i("@@@@ service", "created queue string in " + (System.currentTimeMillis() - start) + " ms");
- ed.putString("queue", q.toString());
- ed.putInt("cardid", mCardId);
- if (mShuffleMode != SHUFFLE_NONE) {
- // In shuffle mode we need to save the history too
- len = mHistory.size();
- q.setLength(0);
- for (int i = 0; i < len; i++) {
- int n = mHistory.get(i);
- if (n == 0) {
- q.append("0;");
- } else {
- while (n != 0) {
- int digit = (n & 0xf);
- n >>= 4;
- q.append(hexdigits[digit]);
- }
- q.append(";");
- }
- }
- ed.putString("history", q.toString());
- }
- }
- ed.putInt("curpos", mPlayPos);
- if (mPlayer.isInitialized()) {
- ed.putLong("seekpos", mPlayer.position());
- }
- ed.putInt("repeatmode", mRepeatMode);
- ed.putInt("shufflemode", mShuffleMode);
- ed.commit();
- //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
- }
- private void reloadQueue() {
- String q = null;
- int id = mCardId;
- if (mPreferences.contains("cardid")) {
- id = mPreferences.getInt("cardid", ~mCardId);
- }
- if (id == mCardId) {
- // Only restore the saved playlist if the card is still
- // the same one as when the playlist was saved
- q = mPreferences.getString("queue", "");
- }
- int qlen = q != null ? q.length() : 0;
- if (qlen > 1) {
- //Log.i("@@@@ service", "loaded queue: " + q);
- int plen = 0;
- int n = 0;
- int shift = 0;
- for (int i = 0; i < qlen; i++) {
- char c = q.charAt(i);
- if (c == ';') {
- ensurePlayListCapacity(plen + 1);
- mPlayList[plen] = n;
- plen++;
- n = 0;
- shift = 0;
- } else {
- if (c >= '0' && c <= '9') {
- n += ((c - '0') << shift);
- } else if (c >= 'a' && c <= 'f') {
- n += ((10 + c - 'a') << shift);
- } else {
- // bogus playlist data
- plen = 0;
- break;
- }
- shift += 4;
- }
- }
- mPlayListLen = plen;
- int pos = mPreferences.getInt("curpos", 0);
- if (pos < 0 || pos >= mPlayListLen) {
- // The saved playlist is bogus, discard it
- mPlayListLen = 0;
- return;
- }
- mPlayPos = pos;
- // When reloadQueue is called in response to a card-insertion,
- // we might not be able to query the media provider right away.
- // To deal with this, try querying for the current file, and if
- // that fails, wait a while and try again. If that too fails,
- // assume there is a problem and don't restore the state.
- Cursor crsr = MusicUtils.query(this,
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- new String [] {"_id"}, "_id=" + mPlayList[mPlayPos] , null, null);
- if (crsr == null || crsr.getCount() == 0) {
- // wait a bit and try again
- SystemClock.sleep(3000);
- crsr = getContentResolver().query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, "_id=" + mPlayList[mPlayPos] , null, null);
- }
- if (crsr != null) {
- crsr.close();
- }
- // Make sure we don't auto-skip to the next song, since that
- // also starts playback. What could happen in that case is:
- // - music is paused
- // - go to UMS and delete some files, including the currently playing one
- // - come back from UMS
- // (time passes)
- // - music app is killed for some reason (out of memory)
- // - music service is restarted, service restores state, doesn't find
- // the "current" file, goes to the next and: playback starts on its
- // own, potentially at some random inconvenient time.
- mOpenFailedCounter = 20;
- mQuietMode = true;
- openCurrent();
- mQuietMode = false;
- if (!mPlayer.isInitialized()) {
- // couldn't restore the saved state
- mPlayListLen = 0;
- return;
- }
- long seekpos = mPreferences.getLong("seekpos", 0);
- seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
- Log.d(LOGTAG, "restored queue, currently at position "
- + position() + "/" + duration()
- + " (requested " + seekpos + ")");
- int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
- if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
- repmode = REPEAT_NONE;
- }
- mRepeatMode = repmode;
- int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
- if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
- shufmode = SHUFFLE_NONE;
- }
- if (shufmode != SHUFFLE_NONE) {
- // in shuffle mode we need to restore the history too
- q = mPreferences.getString("history", "");
- qlen = q != null ? q.length() : 0;
- if (qlen > 1) {
- plen = 0;
- n = 0;
- shift = 0;
- mHistory.clear();
- for (int i = 0; i < qlen; i++) {
- char c = q.charAt(i);
- if (c == ';') {
- if (n >= mPlayListLen) {
- // bogus history data
- mHistory.clear();
- break;
- }
- mHistory.add(n);
- n = 0;
- shift = 0;
- } else {
- if (c >= '0' && c <= '9') {
- n += ((c - '0') << shift);
- } else if (c >= 'a' && c <= 'f') {
- n += ((10 + c - 'a') << shift);
- } else {
- // bogus history data
- mHistory.clear();
- break;
- }
- shift += 4;
- }
- }
- }
- }
- if (shufmode == SHUFFLE_AUTO) {
- if (! makeAutoShuffleList()) {
- shufmode = SHUFFLE_NONE;
- }
- }
- mShuffleMode = shufmode;
- }
- }
- @Override
- public IBinder onBind(Intent intent) {
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mServiceInUse = true;
- return mBinder;
- }
- @Override
- public void onRebind(Intent intent) {
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mServiceInUse = true;
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- mServiceStartId = startId;
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- if (intent != null) {
- String action = intent.getAction();
- String cmd = intent.getStringExtra("command");
- MusicUtils.debugLog("onStartCommand " + action + " / " + cmd);
- if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
- next(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- if (position() < 2000) {
- prev();
- } else {
- seek(0);
- play();
- }
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (isPlaying()) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else {
- play();
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- mPausedByTransientLossOfFocus = false;
- seek(0);
- } else if (CMDSHUFFLE.equals(cmd) || SHUFFLE_ACTION.equals(action)) {
- toggleShuffle();
- } else if (CMDREPEAT.equals(cmd) || REPEAT_ACTION.equals(action)) {
- cycleRepeat();
- }
- }
- // make sure the service will shut down on its own if it was
- // just started but not bound to and nothing is playing
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- return START_STICKY;
- }
- @Override
- public boolean onUnbind(Intent intent) {
- mServiceInUse = false;
- // Take a snapshot of the current playlist
- saveQueue(true);
- if (isPlaying() || mPausedByTransientLossOfFocus) {
- // something is currently playing, or will be playing once
- // an in-progress action requesting audio focus ends, so don't stop the service now.
- return true;
- }
- // If there is a playlist but playback is paused, then wait a while
- // before stopping the service, so that pause/resume isn't slow.
- // Also delay stopping the service if we're transitioning between tracks.
- if (mPlayListLen > 0 || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
- Message msg = mDelayedStopHandler.obtainMessage();
- mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
- return true;
- }
- // No active playlist, OK to stop the service right now
- stopSelf(mServiceStartId);
- return true;
- }
- private Handler mDelayedStopHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // Check again to make sure nothing is playing right now
- if (isPlaying() || mPausedByTransientLossOfFocus || mServiceInUse
- || mMediaplayerHandler.hasMessages(TRACK_ENDED)) {
- return;
- }
- // save the queue again, because it might have changed
- // since the user exited the music app (because of
- // party-shuffle or because the play-position changed)
- saveQueue(true);
- stopSelf(mServiceStartId);
- }
- };
- /**
- * Called when we receive a ACTION_MEDIA_EJECT notification.
- *
- * @param storagePath path to mount point for the removed media
- */
- public void closeExternalStorageFiles(String storagePath) {
- // stop playback and clean up if the SD card is going to be unmounted.
- stop(true);
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- }
- /**
- * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
- * The intent will call closeExternalStorageFiles() if the external media
- * is going to be ejected, so applications can clean up any files they have open.
- */
- public void registerExternalStorageListener() {
- if (mUnmountReceiver == null) {
- mUnmountReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
- saveQueue(true);
- mOneShot = true; // This makes us not save the state again later,
- // which would be wrong because the song ids and
- // card id might not match.
- closeExternalStorageFiles(intent.getData().getPath());
- } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
- mMediaMountedCount++;
- mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
- reloadQueue();
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- }
- }
- };
- IntentFilter iFilter = new IntentFilter();
- iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
- iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- iFilter.addDataScheme("file");
- registerReceiver(mUnmountReceiver, iFilter);
- }
- }
- /**
- * Notify the change-receivers that something has changed.
- * The intent that is sent contains the following data
- * for the currently playing track:
- * "id" - Integer: the database row ID
- * "artist" - String: the name of the artist
- * "album" - String: the name of the album
- * "track" - String: the name of the track
- * The intent has an action that is one of
- * "com.android.music.metachanged"
- * "com.android.music.queuechanged",
- * "com.android.music.playbackcomplete"
- * "com.android.music.playstatechanged"
- * respectively indicating that a new track has
- * started playing, that the playback queue has
- * changed, that playback has stopped because
- * the last file in the list has been played,
- * or that the play-state changed (paused/resumed).
- */
- private void notifyChange(String what) {
- Intent i = new Intent(what);
- i.putExtra("id", Long.valueOf(getAudioId()));
- i.putExtra("artist", getArtistName());
- i.putExtra("album",getAlbumName());
- i.putExtra("track", getTrackName());
- i.putExtra("pos", position());
- i.putExtra("dur", duration());
- sendBroadcast(i);
- if (what.equals(QUEUE_CHANGED)) {
- saveQueue(true);
- } else {
- saveQueue(false);
- }
- // Share this notification directly with our widgets
- mAppWidgetProvider.notifyChange(this, what);
- mAppWidgetProvider2.notifyChange(this, what);
- mAppWidgetProvider3.notifyChange(this, what);
- mAppWidgetProvider4.notifyChange(this, what);
- mAppWidgetProvider5.notifyChange(this, what);
- }
- private void ensurePlayListCapacity(int size) {
- if (mPlayList == null || size > mPlayList.length) {
- // reallocate at 2x requested size so we don't
- // need to grow and copy the array for every
- // insert
- long [] newlist = new long[size * 2];
- int len = mPlayList != null ? mPlayList.length : mPlayListLen;
- for (int i = 0; i < len; i++) {
- newlist[i] = mPlayList[i];
- }
- mPlayList = newlist;
- }
- // FIXME: shrink the array when the needed size is much smaller
- // than the allocated size
- }
- // insert the list of songs at the specified position in the playlist
- private void addToPlayList(long [] list, int position) {
- int addlen = list.length;
- if (position < 0) { // overwrite
- mPlayListLen = 0;
- position = 0;
- }
- ensurePlayListCapacity(mPlayListLen + addlen);
- if (position > mPlayListLen) {
- position = mPlayListLen;
- }
- // move part of list after insertion point
- int tailsize = mPlayListLen - position;
- for (int i = tailsize ; i > 0 ; i--) {
- mPlayList[position + i] = mPlayList[position + i - addlen];
- }
- // copy list into playlist
- for (int i = 0; i < addlen; i++) {
- mPlayList[position + i] = list[i];
- }
- mPlayListLen += addlen;
- }
- /**
- * Appends a list of tracks to the current playlist.
- * If nothing is playing currently, playback will be started at
- * the first track.
- * If the action is NOW, playback will switch to the first of
- * the new tracks immediately.
- * @param list The list of tracks to append.
- * @param action NOW, NEXT or LAST
- */
- public void enqueue(long [] list, int action) {
- synchronized(this) {
- if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
- addToPlayList(list, mPlayPos + 1);
- notifyChange(QUEUE_CHANGED);
- } else {
- // action == LAST || action == NOW || mPlayPos + 1 == mPlayListLen
- addToPlayList(list, Integer.MAX_VALUE);
- notifyChange(QUEUE_CHANGED);
- if (action == NOW) {
- mPlayPos = mPlayListLen - list.length;
- openCurrent();
- play();
- notifyChange(META_CHANGED);
- return;
- }
- }
- if (mPlayPos < 0) {
- mPlayPos = 0;
- openCurrent();
- play();
- notifyChange(META_CHANGED);
- }
- }
- }
- /**
- * Replaces the current playlist with a new list,
- * and prepares for starting playback at the specified
- * position in the list, or a random position if the
- * specified position is 0.
- * @param list The new list of tracks.
- */
- public void open(long [] list, int position) {
- synchronized (this) {
- if (mShuffleMode == SHUFFLE_AUTO) {
- mShuffleMode = SHUFFLE_NORMAL;
- }
- long oldId = getAudioId();
- int listlength = list.length;
- boolean newlist = true;
- if (mPlayListLen == listlength) {
- // possible fast path: list might be the same
- newlist = false;
- for (int i = 0; i < listlength; i++) {
- if (list[i] != mPlayList[i]) {
- newlist = true;
- break;
- }
- }
- }
- if (newlist) {
- addToPlayList(list, -1);
- notifyChange(QUEUE_CHANGED);
- }
- if (position >= 0) {
- mPlayPos = position;
- } else {
- mPlayPos = mRand.nextInt(mPlayListLen);
- }
- mHistory.clear();
- saveBookmarkIfNeeded();
- openCurrent();
- if (oldId != getAudioId()) {
- notifyChange(META_CHANGED);
- }
- }
- }
- /**
- * Moves the item at index1 to index2.
- * @param index1
- * @param index2
- */
- public void moveQueueItem(int index1, int index2) {
- synchronized (this) {
- if (index1 >= mPlayListLen) {
- index1 = mPlayListLen - 1;
- }
- if (index2 >= mPlayListLen) {
- index2 = mPlayListLen - 1;
- }
- if (index1 < index2) {
- long tmp = mPlayList[index1];
- for (int i = index1; i < index2; i++) {
- mPlayList[i] = mPlayList[i+1];
- }
- mPlayList[index2] = tmp;
- if (mPlayPos == index1) {
- mPlayPos = index2;
- } else if (mPlayPos >= index1 && mPlayPos <= index2) {
- mPlayPos--;
- }
- } else if (index2 < index1) {
- long tmp = mPlayList[index1];
- for (int i = index1; i > index2; i--) {
- mPlayList[i] = mPlayList[i-1];
- }
- mPlayList[index2] = tmp;
- if (mPlayPos == index1) {
- mPlayPos = index2;
- } else if (mPlayPos >= index2 && mPlayPos <= index1) {
- mPlayPos++;
- }
- }
- notifyChange(QUEUE_CHANGED);
- }
- }
- /**
- * Returns the current play list
- * @return An array of integers containing the IDs of the tracks in the play list
- */
- public long [] getQueue() {
- synchronized (this) {
- int len = mPlayListLen;
- long [] list = new long[len];
- for (int i = 0; i < len; i++) {
- list[i] = mPlayList[i];
- }
- return list;
- }
- }
- private void openCurrent() {
- synchronized (this) {
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- if (mPlayListLen == 0) {
- return;
- }
- stop(false);
- String id = String.valueOf(mPlayList[mPlayPos]);
- mCursor = getContentResolver().query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, "_id=" + id , null, null);
- if (mCursor != null) {
- mCursor.moveToFirst();
- open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id, false);
- // go to bookmark if needed
- if (isPodcast()) {
- long bookmark = getBookmark();
- // Start playing a little bit before the bookmark,
- // so it's easier to get back in to the narrative.
- seek(bookmark - 5000);
- }
- }
- }
- }
- public void openAsync(String path) {
- synchronized (this) {
- if (path == null) {
- return;
- }
- mRepeatMode = REPEAT_NONE;
- ensurePlayListCapacity(1);
- mPlayListLen = 1;
- mPlayPos = -1;
- mFileToPlay = path;
- mCursor = null;
- mPlayer.setDataSourceAsync(mFileToPlay);
- mOneShot = true;
- }
- }
- /**
- * Opens the specified file and readies it for playback.
- *
- * @param path The full path of the file to be opened.
- * @param oneshot when set to true, playback will stop after this file completes, instead
- * of moving on to the next track in the list
- */
- public void open(String path, boolean oneshot) {
- synchronized (this) {
- if (path == null) {
- return;
- }
- if (oneshot) {
- mRepeatMode = REPEAT_NONE;
- ensurePlayListCapacity(1);
- mPlayListLen = 1;
- mPlayPos = -1;
- }
- // if mCursor is null, try to associate path with a database cursor
- if (mCursor == null) {
- ContentResolver resolver = getContentResolver();
- Uri uri;
- String where;
- String selectionArgs[];
- if (path.startsWith("content://media/")) {
- uri = Uri.parse(path);
- where = null;
- selectionArgs = null;
- } else {
- uri = MediaStore.Audio.Media.getContentUriForPath(path);
- where = MediaStore.Audio.Media.DATA + "=?";
- selectionArgs = new String[] { path };
- }
- try {
- mCursor = resolver.query(uri, mCursorCols, where, selectionArgs, null);
- if (mCursor != null) {
- if (mCursor.getCount() == 0) {
- mCursor.close();
- mCursor = null;
- } else {
- mCursor.moveToNext();
- ensurePlayListCapacity(1);
- mPlayListLen = 1;
- mPlayList[0] = mCursor.getLong(IDCOLIDX);
- mPlayPos = 0;
- }
- }
- } catch (UnsupportedOperationException ex) {
- }
- }
- mFileToPlay = path;
- mPlayer.setDataSource(mFileToPlay);
- mOneShot = oneshot;
- if (! mPlayer.isInitialized()) {
- stop(true);
- if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
- // beware: this ends up being recursive because next() calls open() again.
- if(mPlayPrev == true) {
- prev();
- } else {
- next(false);
- }
- }
- if (! mPlayer.isInitialized() && mOpenFailedCounter != 0) {
- // need to make sure we only shows this once
- mOpenFailedCounter = 0;
- if (!mQuietMode) {
- Toast.makeText(this, R.string.playback_failed, Toast.LENGTH_SHORT).show();
- }
- Log.d(LOGTAG, "Failed to open file for playback");
- }
- } else {
- mOpenFailedCounter = 0;
- }
- }
- }
- /**
- * Starts playback of a previously opened file.
- */
- public void play() {
- mAudioManager.requestAudioFocus(mAu…
Large files files are truncated, but you can click here to view the full file