Merge "Decoupling PlaybackModel from ActiveMediaSourceManager." into pi-dev
diff --git a/car-media-common/src/com/android/car/media/common/ActiveMediaSourceManager.java b/car-media-common/src/com/android/car/media/common/ActiveMediaSourceManager.java
new file mode 100644
index 0000000..6806748
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/ActiveMediaSourceManager.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2018 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.car.media.common;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * This is an abstractions over {@link MediaSessionManager} that provides information about the
+ * currently "active" media session.
+ * <p>
+ * It automatically determines the foreground media app (the one that would normally
+ * receive playback events) and exposes metadata and events from such app, or when a different app
+ * becomes foreground.
+ * <p>
+ * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission to be held by the
+ * calling app.
+ */
+public class ActiveMediaSourceManager {
+ private static final String TAG = "ActiveSourceManager";
+
+ private static final String PLAYBACK_MODEL_SHARED_PREFS =
+ "com.android.car.media.PLAYBACK_MODEL";
+ private static final String PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY =
+ "active_packagename";
+
+ private final MediaSessionManager mMediaSessionManager;
+ private final Handler mHandler = new Handler();
+ private final Context mContext;
+ private final List<Observer> mObservers = new ArrayList<>();
+ private final MediaSessionUpdater mMediaSessionUpdater = new MediaSessionUpdater();
+ private final SharedPreferences mSharedPreferences;
+ @Nullable
+ private MediaController mMediaController;
+ private boolean mIsStarted;
+
+ /**
+ * Temporary work-around to bug b/76017849.
+ * MediaSessionManager is not notifying media session priority changes.
+ * As a work-around we subscribe to playback state changes on all controllers to detect
+ * potential priority changes.
+ * This might cause a few unnecessary checks, but selecting the top-most controller is a
+ * cheap operation.
+ */
+ private class MediaSessionUpdater {
+ private List<MediaController> mControllers = new ArrayList<>();
+
+ private MediaController.Callback mCallback = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ selectMediaController(mMediaSessionManager.getActiveSessions(null));
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ selectMediaController(mMediaSessionManager.getActiveSessions(null));
+ }
+ };
+
+ void setControllersByPackageName(List<MediaController> newControllers) {
+ for (MediaController oldController : mControllers) {
+ oldController.unregisterCallback(mCallback);
+ }
+ for (MediaController newController : newControllers) {
+ newController.registerCallback(mCallback);
+ }
+ mControllers.clear();
+ mControllers.addAll(newControllers);
+ }
+ }
+
+ /**
+ * An observer of this model
+ */
+ public interface Observer {
+ /**
+ * Called when the top source media app changes.
+ */
+ void onActiveSourceChanged();
+ }
+
+ private MediaSessionManager.OnActiveSessionsChangedListener mSessionChangeListener =
+ this::selectMediaController;
+
+ /**
+ * Creates a {@link ActiveMediaSourceManager}. This instance is going to be inactive until
+ * {@link #start()} method is invoked.
+ */
+ public ActiveMediaSourceManager(Context context) {
+ mContext = context;
+ mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
+ mSharedPreferences = mContext.getSharedPreferences(PLAYBACK_MODEL_SHARED_PREFS,
+ Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Selects one of the provided controllers as the "currently playing" one.
+ */
+ private void selectMediaController(List<MediaController> controllers) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ dump("Selecting a media controller from: ", controllers);
+ }
+ changeMediaController(getTopMostController(controllers));
+ mMediaSessionUpdater.setControllersByPackageName(controllers);
+ }
+
+ private void dump(String title, List<MediaController> controllers) {
+ Log.d(TAG, title + " (total: " + controllers.size() + ")");
+ for (MediaController controller : controllers) {
+ String stateName = getStateName(controller.getPlaybackState() != null
+ ? controller.getPlaybackState().getState()
+ : PlaybackState.STATE_NONE);
+ Log.d(TAG, String.format("\t%s: %s",
+ controller.getPackageName(),
+ stateName));
+ }
+ }
+
+ private String getStateName(@PlaybackState.State int state) {
+ switch (state) {
+ case PlaybackState.STATE_NONE:
+ return "NONE";
+ case PlaybackState.STATE_STOPPED:
+ return "STOPPED";
+ case PlaybackState.STATE_PAUSED:
+ return "PAUSED";
+ case PlaybackState.STATE_PLAYING:
+ return "PLAYING";
+ case PlaybackState.STATE_FAST_FORWARDING:
+ return "FORWARDING";
+ case PlaybackState.STATE_REWINDING:
+ return "REWINDING";
+ case PlaybackState.STATE_BUFFERING:
+ return "BUFFERING";
+ case PlaybackState.STATE_ERROR:
+ return "ERROR";
+ case PlaybackState.STATE_CONNECTING:
+ return "CONNECTING";
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ return "SKIPPING_TO_PREVIOUS";
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ return "SKIPPING_TO_NEXT";
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return "SKIPPING_TO_QUEUE_ITEM";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * @return the controller most likely to be the currently active one, out of the list of
+ * active controllers repoted by {@link MediaSessionManager}. It does so by picking the first
+ * one (in order of priority) which an active state as reported by
+ * {@link MediaController#getPlaybackState()}
+ */
+ private MediaController getTopMostController(List<MediaController> controllers) {
+ if (controllers != null && controllers.size() > 0) {
+ for (MediaController candidate : controllers) {
+ @PlaybackState.State int state = candidate.getPlaybackState() != null
+ ? candidate.getPlaybackState().getState()
+ : PlaybackState.STATE_NONE;
+ if (state == PlaybackState.STATE_BUFFERING
+ || state == PlaybackState.STATE_CONNECTING
+ || state == PlaybackState.STATE_FAST_FORWARDING
+ || state == PlaybackState.STATE_PLAYING
+ || state == PlaybackState.STATE_REWINDING
+ || state == PlaybackState.STATE_SKIPPING_TO_NEXT
+ || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
+ || state == PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM) {
+ return candidate;
+ }
+ }
+ // If no source is active, we go for the last known source
+ String packageName = getLastKnownActivePackageName();
+ if (packageName != null) {
+ for (MediaController candidate : controllers) {
+ if (candidate.getPackageName().equals(packageName)) {
+ return candidate;
+ }
+ }
+ }
+ return controllers.get(0);
+ }
+ return null;
+ }
+
+ private void changeMediaController(MediaController mediaController) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "New media controller: " + (mediaController != null
+ ? mediaController.getPackageName() : null));
+ }
+ if ((mediaController == null && mMediaController == null)
+ || (mediaController != null && mMediaController != null
+ && mediaController.getPackageName().equals(mMediaController.getPackageName()))) {
+ // If no change, do nothing.
+ return;
+ }
+ mMediaController = mediaController;
+ setLastKnownActivePackageName(mMediaController != null
+ ? mMediaController.getPackageName()
+ : null);
+ notify(Observer::onActiveSourceChanged);
+ }
+
+ /**
+ * Starts following changes on the list of active media sources. If any changes happen, all
+ * observers registered through {@link #registerObserver(Observer)} will be notified.
+ * <p>
+ * Calling this method might cause an immediate {@link Observer#onActiveSourceChanged()}
+ * event in case the current media source is different than the last known one.
+ */
+ private void start() {
+ mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null);
+ selectMediaController(mMediaSessionManager.getActiveSessions(null));
+ mIsStarted = true;
+ }
+
+ /**
+ * Stops following changes on the list of active media sources. This method could cause an
+ * immediate {@link PlaybackModel.PlaybackObserver#onSourceChanged()} event if a media source
+ * was already connected.
+ */
+ private void stop() {
+ mMediaSessionUpdater.setControllersByPackageName(new ArrayList<>());
+ mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener);
+ changeMediaController(null);
+ mIsStarted = false;
+ }
+
+ private void notify(Consumer<Observer> notification) {
+ mHandler.post(() -> {
+ List<Observer> observers = new ArrayList<>(mObservers);
+ for (Observer observer : observers) {
+ notification.accept(observer);
+ }
+ });
+ }
+
+ /**
+ * @return a {@link MediaController} providing access to metadata of the currently playing media
+ * source, or NULL if no media source has an active session. Changes on this value will
+ * be notified through {@link Observer#onActiveSourceChanged()}
+ */
+ @Nullable
+ public MediaController getMediaController() {
+ return mIsStarted
+ ? mMediaController
+ : getTopMostController(mMediaSessionManager.getActiveSessions(null));
+ }
+
+ /**
+ * Registers an observer to be notified of media events. If the model is not started yet it
+ * will start right away. If the model was already started, the observer will receive an
+ * immediate {@link Observer#onActiveSourceChanged()} event.
+ */
+ public void registerObserver(Observer observer) {
+ mObservers.add(observer);
+ if (!mIsStarted) {
+ start();
+ } else {
+ observer.onActiveSourceChanged();
+ }
+ }
+
+ /**
+ * Unregisters an observer previously registered using
+ * {@link #registerObserver(Observer)}. There are no other observers the model will
+ * stop tracking changes right away.
+ */
+ public void unregisterObserver(Observer observer) {
+ mObservers.remove(observer);
+ if (mObservers.isEmpty() && mIsStarted) {
+ stop();
+ }
+ }
+
+ private String getLastKnownActivePackageName() {
+ return mSharedPreferences.getString(PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY, null);
+ }
+
+ private void setLastKnownActivePackageName(String packageName) {
+ mSharedPreferences.edit()
+ .putString(PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY, packageName)
+ .apply();
+ }
+
+ /**
+ * Returns the {@link MediaController} corresponding to the given package name, or NULL if
+ * no active session exists for it.
+ */
+ public @Nullable MediaController getControllerForPackage(String packageName) {
+ List<MediaController> controllers = mMediaSessionManager.getActiveSessions(null);
+ for (MediaController controller : controllers) {
+ if (controller.getPackageName().equals(packageName)) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the given package name corresponds to the top most media source.
+ */
+ public boolean isPlaying(String packageName) {
+ return mMediaController != null && mMediaController.getPackageName().equals(packageName);
+ }
+}
diff --git a/car-media-common/src/com/android/car/media/common/MediaSource.java b/car-media-common/src/com/android/car/media/common/MediaSource.java
index 6beb309..78bbd64 100644
--- a/car-media-common/src/com/android/car/media/common/MediaSource.java
+++ b/car-media-common/src/com/android/car/media/common/MediaSource.java
@@ -553,21 +553,17 @@
}
/**
- * @return a {@link PlaybackModel} that allows controlling this media source. This method
- * should only be used if this {@link MediaSource} is connected.
- * @see #subscribe(Observer)
+ * Returns a {@link MediaController} that allows controlling this media source, or NULL
+ * if the media source doesn't support browsing or the browser is not connected.
*/
@Nullable
- public PlaybackModel getPlaybackModel() {
- if (mBrowser == null) {
+ public MediaController getMediaController() {
+ if (mBrowser == null || !mBrowser.isConnected()) {
return null;
}
MediaSession.Token token = mBrowser.getSessionToken();
- MediaController controller = new MediaController(mContext, token);
- PlaybackModel playbackModel = new PlaybackModel(mContext);
- playbackModel.setMediaController(controller);
- return playbackModel;
+ return new MediaController(mContext, token);
}
/**
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackControlsActionBar.java b/car-media-common/src/com/android/car/media/common/PlaybackControlsActionBar.java
index 8f131e0..8605690 100644
--- a/car-media-common/src/com/android/car/media/common/PlaybackControlsActionBar.java
+++ b/car-media-common/src/com/android/car/media/common/PlaybackControlsActionBar.java
@@ -99,6 +99,7 @@
if (mModel != null) {
mModel.registerObserver(mObserver);
}
+ updateAccentColor();
}
private void init(Context context) {
@@ -155,10 +156,18 @@
}
private void updateState() {
- mPlayPauseStopImageView.setAction(convertMainAction(mModel.getMainAction()));
- mSpinner.setVisibility(mModel.isBuffering() ? VISIBLE : INVISIBLE);
- mSkipPrevButton.setVisibility(mModel.isSkipPreviewsEnabled() ? VISIBLE : INVISIBLE);
- mSkipNextButton.setVisibility(mModel.isSkipNextEnabled() ? VISIBLE : INVISIBLE);
+ if (mModel != null) {
+ mPlayPauseStopImageView.setVisibility(View.VISIBLE);
+ mPlayPauseStopImageView.setAction(convertMainAction(mModel.getMainAction()));
+ } else {
+ mPlayPauseStopImageView.setVisibility(View.INVISIBLE);
+ }
+ mSpinner.setVisibility(mModel != null && mModel.isBuffering()
+ ? View.VISIBLE : View.INVISIBLE);
+ mSkipPrevButton.setVisibility(mModel != null && mModel.isSkipPreviewsEnabled()
+ ? View.VISIBLE : View.INVISIBLE);
+ mSkipNextButton.setVisibility(mModel != null && mModel.isSkipNextEnabled()
+ ? View.VISIBLE : View.INVISIBLE);
}
@PlayPauseStopImageView.Action
@@ -178,23 +187,31 @@
}
private void updateAccentColor() {
- int defaultColor = mContext.getResources().getColor(android.R.color.background_dark, null);
- MediaSource mediaSource = mModel.getMediaSource();
- int color = mediaSource == null ? defaultColor : mediaSource.getAccentColor(defaultColor);
+ int color = getMediaSourceColor();
int tintColor = ColorChecker.getTintColor(mContext, color);
mPlayPauseStopImageView.setPrimaryActionColor(color, tintColor);
mSpinner.setIndeterminateTintList(ColorStateList.valueOf(color));
}
+ private int getMediaSourceColor() {
+ int defaultColor = mContext.getResources().getColor(android.R.color.background_dark, null);
+ MediaSource mediaSource = mModel != null ? mModel.getMediaSource() : null;
+ return mediaSource != null ? mediaSource.getAccentColor(defaultColor) : defaultColor;
+ }
+
private List<ImageButton> getExtraActions() {
List<ImageButton> extraActions = new ArrayList<>();
- if (mModel.hasQueue()) {
+ if (mModel != null && mModel.hasQueue()) {
extraActions.add(mTrackListButton);
}
return extraActions;
}
private void updateCustomActions() {
+ if (mModel == null) {
+ setViews(new ImageButton[0]);
+ return;
+ }
List<ImageButton> combinedActions = new ArrayList<>();
combinedActions.addAll(getExtraActions());
combinedActions.addAll(mModel.getCustomActions()
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
index a68208f..d11bef0 100644
--- a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
+++ b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
@@ -38,6 +38,7 @@
* hosting application.
*/
public class PlaybackFragment extends Fragment {
+ private ActiveMediaSourceManager mActiveMediaSourceManager;
private PlaybackModel mModel;
private CrossfadeImageView mAlbumBackground;
private PlaybackControls mPlaybackControls;
@@ -47,7 +48,8 @@
private TextView mSubtitle;
private MediaItemMetadata mCurrentMetadata;
- private PlaybackModel.PlaybackObserver mObserver = new PlaybackModel.PlaybackObserver() {
+ private PlaybackModel.PlaybackObserver mPlaybackObserver =
+ new PlaybackModel.PlaybackObserver() {
@Override
public void onSourceChanged() {
updateMetadata();
@@ -58,12 +60,20 @@
updateMetadata();
}
};
+ private ActiveMediaSourceManager.Observer mActiveSourceObserver =
+ new ActiveMediaSourceManager.Observer() {
+ @Override
+ public void onActiveSourceChanged() {
+ mModel.setMediaController(mActiveMediaSourceManager.getMediaController());
+ }
+ };
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.car_playback_fragment, container, false);
+ mActiveMediaSourceManager = new ActiveMediaSourceManager(getContext());
mModel = new PlaybackModel(getContext());
mAlbumBackground = view.findViewById(R.id.album_background);
mPlaybackControls = view.findViewById(R.id.playback_controls);
@@ -97,13 +107,15 @@
@Override
public void onStart() {
super.onStart();
- mModel.registerObserver(mObserver);
+ mActiveMediaSourceManager.registerObserver(mActiveSourceObserver);
+ mModel.registerObserver(mPlaybackObserver);
}
@Override
public void onStop() {
super.onStop();
- mModel.unregisterObserver(mObserver);
+ mActiveMediaSourceManager.unregisterObserver(mActiveSourceObserver);
+ mModel.unregisterObserver(mPlaybackObserver);
}
private void updateMetadata() {
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackModel.java b/car-media-common/src/com/android/car/media/common/PlaybackModel.java
index bfb358d..c472911 100644
--- a/car-media-common/src/com/android/car/media/common/PlaybackModel.java
+++ b/car-media-common/src/com/android/car/media/common/PlaybackModel.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -29,7 +28,6 @@
import android.media.session.MediaController;
import android.media.session.MediaController.TransportControls;
import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.media.session.PlaybackState.Actions;
import android.os.Bundle;
@@ -45,16 +43,8 @@
import java.util.stream.Collectors;
/**
- * View-model for playback UI components. This abstractions provides a simplified view of
- * {@link MediaSession} and {@link MediaSessionManager} data and events.
- *
- * <p>
- * It automatically determines the foreground media app (the one that would normally
- * receive playback events) and exposes metadata and events from such app, or when a different app
- * becomes foreground.
- * <p>
- * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
- * permission be held by the calling app.
+ * Wrapper of {@link MediaSession}. It provides access to media session events and extended
+ * information on the currently playing item metadata.
*/
public class PlaybackModel {
private static final String TAG = "PlaybackModel";
@@ -62,56 +52,14 @@
private static final String ACTION_SET_RATING =
"com.android.car.media.common.ACTION_SET_RATING";
private static final String EXTRA_SET_HEART = "com.android.car.media.common.EXTRA_SET_HEART";
- private static final String PLAYBACK_MODEL_SHARED_PREFS =
- "com.android.car.media.PLAYBACK_MODEL";
- private static final String PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY =
- "active_packagename";
- private final MediaSessionManager mMediaSessionManager;
private final Handler mHandler = new Handler();
@Nullable
+ private final Context mContext;
+ private final List<PlaybackObserver> mObservers = new ArrayList<>();
private MediaController mMediaController;
- private Context mContext;
- private List<PlaybackObserver> mObservers = new ArrayList<>();
- private final MediaSessionUpdater mMediaSessionUpdater = new MediaSessionUpdater();
private MediaSource mMediaSource;
private boolean mIsStarted;
- private SharedPreferences mSharedPreferences;
-
- /**
- * Temporary work-around to bug b/76017849.
- * MediaSessionManager is not notifying media session priority changes.
- * As a work-around we subscribe to playback state changes on all controllers to detect
- * potential priority changes.
- * This might cause a few unnecessary checks, but selecting the top-most controller is a
- * cheap operation.
- */
- private class MediaSessionUpdater {
- private List<MediaController> mControllers = new ArrayList<>();
-
- private MediaController.Callback mCallback = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- selectMediaController(mMediaSessionManager.getActiveSessions(null));
- }
-
- @Override
- public void onSessionDestroyed() {
- selectMediaController(mMediaSessionManager.getActiveSessions(null));
- }
- };
-
- void setControllersByPackageName(List<MediaController> newControllers) {
- for (MediaController oldController : mControllers) {
- oldController.unregisterCallback(mCallback);
- }
- for (MediaController newController : newControllers) {
- newController.registerCallback(mCallback);
- }
- mControllers.clear();
- mControllers.addAll(newControllers);
- }
- }
/**
* An observer of this model
@@ -151,119 +99,29 @@
}
};
- private MediaSessionManager.OnActiveSessionsChangedListener mSessionChangeListener =
- this::selectMediaController;
+ /**
+ * Creates a {@link PlaybackModel}
+ */
+ public PlaybackModel(@NonNull Context context) {
+ this(context, null);
+ }
/**
- * Creates a {@link PlaybackModel}. This instance is going to be inactive until
- * {@link #start()} method is invoked.
+ * Creates a {@link PlaybackModel} wrapping to the given media controller
*/
- public PlaybackModel(Context context) {
+ public PlaybackModel(@NonNull Context context, @Nullable MediaController controller) {
mContext = context;
- mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
- mSharedPreferences = mContext.getSharedPreferences(PLAYBACK_MODEL_SHARED_PREFS,
- Context.MODE_PRIVATE);
+ changeMediaController(controller);
}
/**
* Sets the {@link MediaController} wrapped by this model.
*/
- public void setMediaController(MediaController controller) {
- changeMediaController(controller);
+ public void setMediaController(@Nullable MediaController mediaController) {
+ changeMediaController(mediaController);
}
- /**
- * Selects one of the provided controllers as the "currently playing" one.
- */
- private void selectMediaController(List<MediaController> controllers) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- dump("Selecting a media controller from: ", controllers);
- }
- changeMediaController(getTopMostController(controllers));
- mMediaSessionUpdater.setControllersByPackageName(controllers);
- }
-
- private void dump(String title, List<MediaController> controllers) {
- Log.d(TAG, title + " (total: " + controllers.size() + ")");
- for (MediaController controller : controllers) {
- String stateName = getStateName(controller.getPlaybackState() != null
- ? controller.getPlaybackState().getState()
- : PlaybackState.STATE_NONE);
- Log.d(TAG, String.format("\t%s: %s",
- controller.getPackageName(),
- stateName));
- }
- }
-
- private String getStateName(@PlaybackState.State int state) {
- switch (state) {
- case PlaybackState.STATE_NONE:
- return "NONE";
- case PlaybackState.STATE_STOPPED:
- return "STOPPED";
- case PlaybackState.STATE_PAUSED:
- return "PAUSED";
- case PlaybackState.STATE_PLAYING:
- return "PLAYING";
- case PlaybackState.STATE_FAST_FORWARDING:
- return "FORWARDING";
- case PlaybackState.STATE_REWINDING:
- return "REWINDING";
- case PlaybackState.STATE_BUFFERING:
- return "BUFFERING";
- case PlaybackState.STATE_ERROR:
- return "ERROR";
- case PlaybackState.STATE_CONNECTING:
- return "CONNECTING";
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- return "SKIPPING_TO_PREVIOUS";
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- return "SKIPPING_TO_NEXT";
- case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
- return "SKIPPING_TO_QUEUE_ITEM";
- default:
- return "UNKNOWN";
- }
- }
-
- /**
- * @return the controller most likely to be the currently active one, out of the list of
- * active controllers repoted by {@link MediaSessionManager}. It does so by picking the first
- * one (in order of priority) which an active state as reported by
- * {@link MediaController#getPlaybackState()}
- */
- private MediaController getTopMostController(List<MediaController> controllers) {
- if (controllers != null && controllers.size() > 0) {
- for (MediaController candidate : controllers) {
- @PlaybackState.State int state = candidate.getPlaybackState() != null
- ? candidate.getPlaybackState().getState()
- : PlaybackState.STATE_NONE;
- if (state == PlaybackState.STATE_BUFFERING
- || state == PlaybackState.STATE_CONNECTING
- || state == PlaybackState.STATE_FAST_FORWARDING
- || state == PlaybackState.STATE_PLAYING
- || state == PlaybackState.STATE_REWINDING
- || state == PlaybackState.STATE_SKIPPING_TO_NEXT
- || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
- || state == PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM) {
- return candidate;
- }
- }
- // If no source is active, we go for the last known source
- String packageName = getLastKnownActivePackageName();
- if (packageName != null) {
- for (MediaController candidate : controllers) {
- if (candidate.getPackageName().equals(packageName)) {
- return candidate;
- }
- }
- }
- return controllers.get(0);
- }
- return null;
- }
-
- private void changeMediaController(MediaController mediaController) {
+ private void changeMediaController(@Nullable MediaController mediaController) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "New media controller: " + (mediaController != null
? mediaController.getPackageName() : null));
@@ -278,40 +136,35 @@
mMediaController.unregisterCallback(mCallback);
}
mMediaController = mediaController;
- setLastKnownActivePackageName(mMediaController != null
- ? mMediaController.getPackageName()
- : null);
- if (mMediaController != null) {
- mMediaSource = new MediaSource(mContext, mMediaController.getPackageName());
+ mMediaSource = mMediaController != null
+ ? new MediaSource(mContext, mMediaController.getPackageName()) : null;
+ if (mMediaController != null && mIsStarted) {
mMediaController.registerCallback(mCallback);
- } else {
- mMediaSource = null;
}
- notify(PlaybackObserver::onSourceChanged);
+ if (mIsStarted) {
+ notify(PlaybackObserver::onSourceChanged);
+ }
}
/**
- * Starts following changes on the list of active media sources. If any changes happen, all
- * observers registered through {@link #registerObserver(PlaybackObserver)} will be notified.
- * <p>
- * Calling this method might cause an immediate {@link PlaybackObserver#onSourceChanged()}
- * event in case the current media source is different than the last known one.
+ * Starts following changes on the playback state of the given source. If any changes happen,
+ * all observers registered through {@link #registerObserver(PlaybackObserver)} will be
+ * notified.
*/
private void start() {
- mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null);
- selectMediaController(mMediaSessionManager.getActiveSessions(null));
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mCallback);
+ }
mIsStarted = true;
}
/**
- * Stops following changes on the list of active media sources. This method could cause an
- * immediate {@link PlaybackObserver#onSourceChanged()} event if a media source was already
- * connected.
+ * Stops following changes on the list of active media sources.
*/
private void stop() {
- mMediaSessionUpdater.setControllersByPackageName(new ArrayList<>());
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener);
- changeMediaController(null);
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mCallback);
+ }
mIsStarted = false;
}
@@ -326,20 +179,20 @@
/**
* @return a {@link MediaSource} providing access to metadata of the currently playing media
- * source, or NULL if no media source has an active session. Changes on this value will
- * be notified through {@link PlaybackObserver#onSourceChanged()}
+ * source, or NULL if the media source has no active session.
*/
@Nullable
public MediaSource getMediaSource() {
- if (mIsStarted) {
- return mMediaSource;
- }
+ return mMediaSource;
+ }
- MediaController controller = getTopMostController(mMediaSessionManager
- .getActiveSessions(null));
- return controller != null
- ? new MediaSource(mContext, controller.getPackageName())
- : null;
+ /**
+ * @return a {@link MediaController} that can be used to control this media source, or NULL
+ * if the media source has no active session.
+ */
+ @Nullable
+ public MediaController getMediaController() {
+ return mMediaController;
}
/**
@@ -399,7 +252,6 @@
if (mMediaController != null) {
mMediaController.getTransportControls().skipToPrevious();
}
-
}
/**
@@ -445,9 +297,7 @@
cntrl.sendCustomAction(action, extras);
}
- if (mMediaController != null) {
- mMediaController.getTransportControls().sendCustomAction(action, extras);
- }
+ mMediaController.getTransportControls().sendCustomAction(action, extras);
}
/**
@@ -743,14 +593,4 @@
return null;
}
}
-
- private String getLastKnownActivePackageName() {
- return mSharedPreferences.getString(PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY, null);
- }
-
- private void setLastKnownActivePackageName(String packageName) {
- mSharedPreferences.edit()
- .putString(PLAYBACK_MODEL_ACTIVE_PACKAGE_NAME_KEY, packageName)
- .apply();
- }
}