/* * Copyright (C) 2014 AChep@xda <ynkr.wang@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.bullmobi.message.services.media; import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.graphics.Bitmap; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import com.bullmobi.message.R; import com.bullmobi.message.services.MediaService; import com.bullmobi.base.tests.Check; import java.util.List; import static com.bullmobi.base.Build.DEBUG; /** * {@inheritDoc} */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) class MediaController2Lollipop extends MediaController2 { @Nullable private MediaController mMediaController; private boolean mSessionListening; private final ComponentName mComponent; private final MediaSessionManager mMSManager; private final MediaController.Callback mCallback = new MediaController.Callback() { @Override public void onMetadataChanged(MediaMetadata metadata) { super.onMetadataChanged(metadata); Check.getInstance().isInMainThread(); updateMetadata(metadata); } @Override public void onPlaybackStateChanged(PlaybackState state) { super.onPlaybackStateChanged(state); Check.getInstance().isInMainThread(); updatePlaybackState(state.getState()); } }; private final MediaSessionManager.OnActiveSessionsChangedListener mSessionListener = new MediaSessionManager.OnActiveSessionsChangedListener() { @Override public void onActiveSessionsChanged(List<MediaController> controllers) { Check.getInstance().isInMainThread(); if (mMediaController != null) { for (MediaController controller : controllers) { if (mMediaController == controller) { // Current media controller is still alive. return; } } } MediaController mc = pickBestMediaController(controllers); if (mc != null) { setMediaController(mc); } else { clearMediaController(true); } } @Nullable private MediaController pickBestMediaController( @NonNull List<MediaController> list) { if (DEBUG) Log.d(TAG, "Media controllers count:" + list.size()); int mediaControllerScore = -1; MediaController mediaController = null; for (MediaController mc : list) { if (mc == null) continue; int mcScore = 0; // Check for the current state PlaybackState state = mc.getPlaybackState(); if (state != null) { switch (state.getState()) { case PlaybackState.STATE_STOPPED: case PlaybackState.STATE_ERROR: break; default: mcScore++; break; } } if (mcScore > mediaControllerScore) { mediaControllerScore = mcScore; mediaController = mc; } } return mediaController; } }; /** * {@inheritDoc} */ protected MediaController2Lollipop(@NonNull Context context) { super(context); mMSManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); mComponent = new ComponentName(context, MediaService.class); } /** * {@inheritDoc} */ @Override public void onStart(Object... objects) { super.onStart(); try { mMSManager.addOnActiveSessionsChangedListener(mSessionListener, mComponent); mSessionListener.onActiveSessionsChanged(mMSManager.getActiveSessions(mComponent)); mSessionListening = true; } catch (SecurityException exception) { Log.w(TAG, "Failed to start Lollipop media controller: " + exception.getMessage()); mSessionListening = false; // Media controller needs notification listener service // permissions to be granted. } } /** * {@inheritDoc} */ @Override public void onStop(Object... objects) { if (mSessionListening) { mMSManager.removeOnActiveSessionsChangedListener(mSessionListener); clearMediaController(true); } super.onStop(); } private void clearMediaController(boolean clear) { if (mMediaController != null) { mMediaController.unregisterCallback(mCallback); mMediaController = null; if (clear) { clearMetadata(); updatePlaybackState(null); } } } private void setMediaController(@NonNull MediaController controller) { if (DEBUG) Log.d(TAG, "Switching to \'" + controller.getPackageName() + "\' controller."); clearMediaController(false); mMediaController = controller; mMediaController.registerCallback(mCallback); updatePlaybackState(mMediaController.getPlaybackState()); updateMetadata(mMediaController.getMetadata()); } /** * {@inheritDoc} */ public void sendMediaAction(int action) { if (mMediaController == null) { // Maybe somebody is waiting to start his player by // this lovely event. // TODO: Check if it works as expected. MediaController2.broadcastMediaAction(mContext, action); return; } MediaController.TransportControls controls = mMediaController.getTransportControls(); switch (action) { case ACTION_PLAY_PAUSE: if (mPlaybackState == PlaybackState.STATE_PLAYING) { controls.pause(); } else { controls.play(); } break; case ACTION_STOP: controls.stop(); break; case ACTION_SKIP_TO_NEXT: controls.skipToNext(); break; case ACTION_SKIP_TO_PREVIOUS: controls.skipToPrevious(); break; default: throw new IllegalArgumentException(); } } /** * {@inheritDoc} */ @Override public void seekTo(long position) { if (mMediaController == null) { // Do nothing or crash? return; } mMediaController.getTransportControls().seekTo(position); } /** * {@inheritDoc} */ @Override public long getPlaybackBufferedPosition() { if (mMediaController == null) { // Do nothing or crash? return -1; } return mMediaController.getPlaybackState().getBufferedPosition(); } /** * {@inheritDoc} */ @Override public long getPlaybackPosition() { if (mMediaController == null) { // Do nothing or crash? return -1; } return mMediaController.getPlaybackState().getPosition(); } /** * Clears {@link #mMetadata metadata}. Same as calling * {@link #updateMetadata(MediaMetadata)} * with {@code null} parameter. * * @see #updateMetadata(MediaMetadata) */ private void clearMetadata() { updateMetadata(null); } /** * Updates {@link #mMetadata metadata} from given media metadata class. * This also updates play state. * * @param data Object of metadata to update from, or {@code null} to clear local metadata. * @see #clearMetadata() */ private void updateMetadata(@Nullable MediaMetadata data) { if (data == null) { if (mMetadata.isEmpty()) { // No need to clear it again nor // notify subscribers about it. return; } mMetadata.clear(); } else { final String id = data.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); if (id != null && id.equals(mMetadata.id)) return; mMetadata.id = id; mMetadata.title = data.getDescription().getTitle(); mMetadata.artist = data.getText(MediaMetadata.METADATA_KEY_ARTIST); mMetadata.album = data.getText(MediaMetadata.METADATA_KEY_ALBUM); mMetadata.duration = data.getLong(MediaMetadata.METADATA_KEY_DURATION); mMetadata.generateSubtitle(); // Load the artwork Bitmap artwork = data.getBitmap(MediaMetadata.METADATA_KEY_ART); if (artwork == null) { artwork = data.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); // Might still be null } if (artwork != null) { final int size = mContext.getResources().getDimensionPixelSize(R.dimen.media_artwork_size); try { mMetadata.bitmap = Bitmap.createScaledBitmap(artwork, size, size, true); } catch (OutOfMemoryError e) { mMetadata.bitmap = null; } } else { mMetadata.bitmap = null; // Clear previous artwork } } notifyOnMetadataChanged(); } private void updatePlaybackState(@Nullable PlaybackState state) { updatePlaybackState(state == null ? PlaybackState.STATE_NONE : state.getState()); } }