/src/com/android/music/MediaPlaybackService.java
Java | 2181 lines | 1697 code | 167 blank | 317 comment | 390 complexity | eac07fbf1c949d110b1c15e4cb7bbe1a 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
- * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
- *
- * 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.NotificationManager;
- 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.graphics.Bitmap;
- import android.media.audiofx.AudioEffect;
- import android.media.AudioManager;
- import android.media.AudioManager.OnAudioFocusChangeListener;
- import android.media.MediaMetadataRetriever;
- import android.media.MediaPlayer;
- import android.media.RemoteControlClient;
- import android.media.RemoteControlClient.MetadataEditor;
- 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.provider.MediaStore;
- import android.util.Log;
- import android.view.KeyEvent;
- 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 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 CMDPLAY = "play";
- public static final String CMDPREVIOUS = "previous";
- public static final String CMDNEXT = "next";
- public static final String CMDNOTIF = "buttonId";
- 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";
- private static final String PLAYSTATUS_REQUEST = "com.android.music.playstatusrequest";
- private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse";
- 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 FOCUSCHANGE = 4;
- private static final int FADEDOWN = 5;
- private static final int FADEUP = 6;
- private static final int MAX_HISTORY_SIZE = 100;
- private Notification status;
- 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 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 BroadcastReceiver mA2dpReceiver = null;
- private WakeLock mWakeLock;
- private int mServiceStartId = -1;
- private boolean mServiceInUse = false;
- private boolean mIsSupposedToBePlaying = false;
- private boolean mQuietMode = false;
- private AudioManager mAudioManager;
- private boolean mQueueIsSaveable = true;
- // used to track what type of audio focus loss caused the playback to pause
- public static boolean mInternalPause = false;
- // extending Transient Loss of focus for Bluetooth Suspend using Cause
- public final static int PAUSE_REASON_NONE = 0;
- public final static int PAUSE_BY_TRANSIENT_LOSS_OF_FOCUS = 1;
- public final static int PAUSE_DUE_TO_A2DP_SUSPEND =2;
- public static int mPauseReason = PAUSE_REASON_NONE;
- // when BluetoothA2DPService is available in Public SDK the below need not
- // be redefined
- public static final String EXTRA_SINK_STATE =
- "android.bluetooth.a2dp.extra.SINK_STATE";
- public static final String EXTRA_PREVIOUS_SINK_STATE =
- "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE";
- public static final String ACTION_SINK_STATE_CHANGED =
- "android.bluetooth.a2dp.action.SINK_STATE_CHANGED";
- public static final int STATE_DISCONNECTED = 0;
- public static final int STATE_CONNECTING = 1;
- public static final int STATE_CONNECTED = 2;
- public static final int STATE_DISCONNECTING = 3;
- public static final int STATE_PLAYING = 4;
- 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();
- // interval after which we stop the service when idle
- private static final int IDLE_DELAY = 60000;
- private RemoteControlClient mRemoteControlClient;
- 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 FADEDOWN:
- mCurrentVolume -= .05f;
- if (mCurrentVolume > .2f) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEDOWN, 10);
- } else {
- mCurrentVolume = .2f;
- }
- mPlayer.setVolume(mCurrentVolume);
- break;
- case FADEUP:
- mCurrentVolume += .01f;
- if (mCurrentVolume < 1.0f) {
- mMediaplayerHandler.sendEmptyMessageDelayed(FADEUP, 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 {
- next(false);
- }
- break;
- case RELEASE_WAKELOCK:
- mWakeLock.release();
- break;
- case FOCUSCHANGE:
- // This code is here so we can better synchronize it with the code that
- // handles fade-in
- switch (msg.arg1) {
- case AudioManager.AUDIOFOCUS_LOSS:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
- if(isPlaying()) {
- mInternalPause = false;
- }
- pause();
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- mMediaplayerHandler.removeMessages(FADEUP);
- mMediaplayerHandler.sendEmptyMessage(FADEDOWN);
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
- if(isPlaying()) {
- mInternalPause = true;
- mPauseReason = PAUSE_BY_TRANSIENT_LOSS_OF_FOCUS;
- }
- pause();
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
- if(!isPlaying() && (mInternalPause) &&
- (PAUSE_BY_TRANSIENT_LOSS_OF_FOCUS == mPauseReason)) {
- mInternalPause = false;
- mCurrentVolume = 0f;
- mPlayer.setVolume(mCurrentVolume);
- play(); // also queues a fade-in
- } else {
- mMediaplayerHandler.removeMessages(FADEDOWN);
- mMediaplayerHandler.sendEmptyMessage(FADEUP);
- }
- break;
- default:
- Log.e(LOGTAG, "Unknown audio focus change code");
- }
- break;
- default:
- break;
- }
- }
- };
- 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)) {
- next(true);
- } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
- prev();
- } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
- if (isPlaying()) {
- pause();
- mInternalPause = false;
- } else {
- play();
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mInternalPause = false;
- } else if (CMDPLAY.equals(cmd)) {
- play();
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- mInternalPause = false;
- seek(0);
- } 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);
- }
- }
- };
- private BroadcastReceiver mA2dpUpdateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- float mCurrentVolume = 1.0f;
- if( action.equals(ACTION_SINK_STATE_CHANGED) ) {
- int curState = intent.getIntExtra(EXTRA_SINK_STATE,
- STATE_DISCONNECTED);
- int prevState = intent.getIntExtra(EXTRA_PREVIOUS_SINK_STATE,
- STATE_DISCONNECTED);
- if( (curState == STATE_CONNECTED) &&
- (prevState == STATE_PLAYING ) &&
- (isPlaying()) ) {
- mInternalPause = true;
- mPauseReason = PAUSE_DUE_TO_A2DP_SUSPEND;
- pause();
- } else if( (curState == STATE_PLAYING) &&
- (prevState == STATE_CONNECTED) &&
- (!isPlaying()) &&
- (mInternalPause) &&
- (PAUSE_DUE_TO_A2DP_SUSPEND == mPauseReason)) {
- mInternalPause = false;
- mCurrentVolume = 0f;
- mPlayer.setVolume(mCurrentVolume);
- play(); // also queues a fade-in
- }
- }
- }
- };
- private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
- public void onAudioFocusChange(int focusChange) {
- mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
- }
- };
- public MediaPlaybackService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- ComponentName rec = new ComponentName(getPackageName(),
- MediaButtonIntentReceiver.class.getName());
- mAudioManager.registerMediaButtonEventReceiver(rec);
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.setComponent(rec);
- PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
- 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
- mAudioManager.registerRemoteControlClient(mRemoteControlClient);
- int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
- | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
- | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
- | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_STOP;
- mRemoteControlClient.setTransportControlFlags(flags);
-
- mPreferences = getSharedPreferences("Music", MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
- mCardId = MusicUtils.getCardId(this);
- registerExternalStorageListener();
- registerA2dpServiceListener();
- // Needs to be done in this thread, since otherwise ApplicationContext.getPowerManager() crashes.
- mPlayer = new MultiPlayer();
- mPlayer.setHandler(mMediaplayerHandler);
- reloadQueue();
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- 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(PLAYSTATUS_REQUEST);
- registerReceiver(mIntentReceiver, commandFilter);
- //handling A2DP state changes for remote suspend feature
- IntentFilter btCommandFilter = new IntentFilter();
- btCommandFilter.addAction(ACTION_SINK_STATE_CHANGED);
- registerReceiver(mA2dpUpdateReceiver, btCommandFilter);
- 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
- Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
- i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
- i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
- sendBroadcast(i);
- mPlayer.release();
- mPlayer = null;
- mAudioManager.abandonAudioFocus(mAudioFocusListener);
- mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
-
- // make sure there aren't any other messages coming
- mDelayedStopHandler.removeCallbacksAndMessages(null);
- mMediaplayerHandler.removeCallbacksAndMessages(null);
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- unregisterReceiver(mA2dpUpdateReceiver);
- unregisterReceiver(mIntentReceiver);
- unregisterReceiver(mA2dpReceiver);
- 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 void saveQueue(boolean full) {
- if (!mQueueIsSaveable) {
- 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) {
- continue;
- } else 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);
- SharedPreferencesCompat.apply(ed);
- //Log.i("@@@@ service", "saved state in " + (System.currentTimeMillis() - start) + " ms");
- }
- private void reloadQueue() {
- String q = null;
- boolean newstyle = false;
- int id = mCardId;
- if (mPreferences.contains("cardid")) {
- newstyle = true;
- 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();
- mInternalPause = false;
- } else {
- play();
- }
- } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
- pause();
- mInternalPause = false;
- } else if (CMDPLAY.equals(cmd)) {
- play();
- } else if (CMDSTOP.equals(cmd)) {
- pause();
- if (intent.getIntExtra(CMDNOTIF, 0) == 3) {
- stopForeground(true);
- }
- mInternalPause = false;
- seek(0);
- } else if (PLAYSTATUS_REQUEST.equals(action)) {
- notifyChange(PLAYSTATUS_RESPONSE);
- }
- }
- // 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() || mInternalPause) {
- // 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() || mInternalPause || 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);
- mQueueIsSaveable = false;
- closeExternalStorageFiles(intent.getData().getPath());
- } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
- mMediaMountedCount++;
- mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
- reloadQueue();
- mQueueIsSaveable = true;
- 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);
- }
- }
- public void registerA2dpServiceListener() {
- mA2dpReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(PLAYSTATUS_REQUEST)) {
- notifyChange(PLAYSTATUS_RESPONSE);
- }
- }
- };
- IntentFilter iFilter = new IntentFilter();
- iFilter.addAction(PLAYSTATUS_REQUEST);
- registerReceiver(mA2dpReceiver, 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);
- try {
- if (mCursor == null)
- i.putExtra("id", -1);
- else
- i.putExtra("id", mCursor.getLong(mCursor.getColumnIndexOrThrow("_id")));
- Cursor cu = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1", null, null);
- if (cu == null)
- i.putExtra("ListSize", Long.valueOf(-1));
- else {
- i.putExtra("ListSize", Long.valueOf(cu.getCount()));
- cu.close();
- }
- } catch (Exception e) { Log.e(LOGTAG, "Exception: " + e); }
- i.putExtra("artist", getArtistName());
- i.putExtra("album",getAlbumName());
- i.putExtra("track", getTrackName());
- i.putExtra("playing", isPlaying());
- i.putExtra("duration", duration());
- i.putExtra("position", position());
- sendStickyBroadcast(i);
- if (what.equals(PLAYSTATE_CHANGED)) {
- mRemoteControlClient.setPlaybackState(isPlaying() ?
- RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
- } else if (what.equals(META_CHANGED)) {
- RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
- ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
- ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
- ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
- ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
- Bitmap b = MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), true);
- if (b != null) {
- ed.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, b);
- }
- ed.apply();
- }
- if (what.equals(QUEUE_CHANGED)) {
- saveQueue(true);
- } else {
- saveQueue(false);
- }
- // Share this notification directly with our widgets
- mAppWidgetProvider.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;
- if (mPlayListLen == 0) {
- mCursor.close();
- mCursor = null;
- notifyChange(META_CHANGED);
- }
- }
- /**
- * 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);
- }
- int oldpos = mPlayPos;
- 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);
- // 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);
- }
- }
- }
- }
- /**
- * Opens the specified file and readies it for playback.
- *
- * @param path The full path of the file to be opened.
- */
- public void open(String path) {
- synchronized (this) {
- if (path == null) {
- return;
- }
- // 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;
- }
- }
- String fileName = getTrackName();
- Log.e("MediaPlaybackActivity", "AUDIOFILENAME IS : " +fileName);
- } catch (UnsupportedOperationException ex) {
- }
- }
- mFileToPlay = path;
- mPlayer.setDataSource(mFileToPlay);
- 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(mAudioFocusListener, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- mAudioManager.registerMediaButtonEventReceiver(new ComponentName(this.getPackageName(),
- MediaButtonIntentReceiver.class.getName()));
- if (mPlayer.isInitialized()) {
- // if we are at the end of the song, go to the next song first
- long duration = mPlayer.duration();
- if (mRepeatMode != REPEAT_CURRENT && duration > 1000 &&
- mPlayer.position() >= duration - 1000) {
- next(true);
- }
- mPlayer.start();
- // make sure we fade in, in case a previous fadein was stopped because
- // of another focus loss
- mMediaplayerHandler.removeMessages(FADEDOWN);
- mMediaplayerHandler.sendEmptyMessage(FADEUP);
- RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
- views.setImageViewBitmap(R.id.albumArt, MusicUtils.getArtwork(this, getAudioId(), getAlbumId(), true));
- ComponentName rec = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.putExtra(CMDNOTIF, 1);
- mediaButtonIntent.setComponent(rec);
- KeyEvent mediaKey = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCOD…
Large files files are truncated, but you can click here to view the full file