summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSrinivas Visvanathan <sriniv@google.com>2017-03-22 09:38:08 -0700
committerSrinivas Visvanathan <sriniv@google.com>2017-03-24 15:55:49 -0700
commit5cb58af61618d6c457685a5feba630540f25567c (patch)
treea62727bd35c20e7bf549084aa44dbf6c87fcd136
parentef5f2af55c10d097d166d81b28fc537028e21a66 (diff)
downloadMedia-android-vts-8.0_r9.tar.gz
Moving Media app off CarActivityandroid-vts-8.0_r9android-vts-8.0_r8android-vts-8.0_r7android-vts-8.0_r6android-vts-8.0_r2android-vts-8.0_r13android-vts-8.0_r12android-vts-8.0_r11android-vts-8.0_r10android-vts-8.0_r1android-security-8.0.0_r54android-security-8.0.0_r53android-security-8.0.0_r52android-o-mr1-preview-2android-o-mr1-preview-1android-cts-8.0_r9android-cts-8.0_r8android-cts-8.0_r7android-cts-8.0_r6android-cts-8.0_r5android-cts-8.0_r4android-cts-8.0_r3android-cts-8.0_r26android-cts-8.0_r25android-cts-8.0_r24android-cts-8.0_r23android-cts-8.0_r22android-cts-8.0_r21android-cts-8.0_r20android-cts-8.0_r2android-cts-8.0_r19android-cts-8.0_r18android-cts-8.0_r17android-cts-8.0_r16android-cts-8.0_r15android-cts-8.0_r14android-cts-8.0_r13android-cts-8.0_r12android-cts-8.0_r11android-cts-8.0_r10android-cts-8.0_r1android-8.0.0_r9android-8.0.0_r7android-8.0.0_r51android-8.0.0_r50android-8.0.0_r49android-8.0.0_r48android-8.0.0_r47android-8.0.0_r46android-8.0.0_r45android-8.0.0_r44android-8.0.0_r43android-8.0.0_r42android-8.0.0_r41android-8.0.0_r40android-8.0.0_r4android-8.0.0_r39android-8.0.0_r38android-8.0.0_r37android-8.0.0_r36android-8.0.0_r35android-8.0.0_r32android-8.0.0_r31android-8.0.0_r30android-8.0.0_r3android-8.0.0_r29android-8.0.0_r28android-8.0.0_r2android-8.0.0_r17android-8.0.0_r16android-8.0.0_r15android-8.0.0_r13android-8.0.0_r12android-8.0.0_r11android-8.0.0_r10android-8.0.0_r1security-oc-releaseoreo-vts-releaseoreo-security-releaseoreo-releaseoreo-r6-releaseoreo-r5-releaseoreo-r4-releaseoreo-r3-releaseoreo-r2-releaseoreo-devoreo-cts-release
- MediaActivity now extends CarDrawerActivity from car-stream-ui-lib and no longer relies on legacy CarActivity. - Re-implemented Drawer logic using CarDrawerAdapter. Added media-specific subclass: MediaDrawerAdapter. It relies on MediaBrowserItemsFetcher (and subclasses) for actual fetching of browse items or queue items. - Removed now dead classes: MediaProxyActivity, MediaCarMenuCallbacks and MediaMenuBitmapDownloader. - Drawer layout is still a bit broken because CarDrawerAdapter is not flexible enough for Media needs. Filed follow-on bug b/36573125 to address. Bug: 34352155 Test: Played music in from LocalMediaPlay and BT Media player. Local version works for the most part (except for b/36571620). BT version browse works, but playback is buggy. Need to investigate BT side of things. Change-Id: Ic9ee87fcdeaecb2c71a9d354316c1342ae90de28
-rw-r--r--AndroidManifest.xml4
-rw-r--r--src/com/android/car/media/MediaActivity.java73
-rw-r--r--src/com/android/car/media/MediaCarMenuCallbacks.java618
-rw-r--r--src/com/android/car/media/MediaMenuBitmapDownloader.java157
-rw-r--r--src/com/android/car/media/MediaPlaybackFragment.java28
-rw-r--r--src/com/android/car/media/MediaPlaybackModel.java63
-rw-r--r--src/com/android/car/media/MediaProxyActivity.java26
-rw-r--r--src/com/android/car/media/drawer/MediaBrowserItemsFetcher.java169
-rw-r--r--src/com/android/car/media/drawer/MediaDrawerAdapter.java76
-rw-r--r--src/com/android/car/media/drawer/MediaDrawerController.java123
-rw-r--r--src/com/android/car/media/drawer/MediaItemsFetcher.java104
-rw-r--r--src/com/android/car/media/drawer/MediaQueueItemsFetcher.java142
12 files changed, 717 insertions, 866 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7900c7f9..2f2baa37 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -30,8 +30,8 @@
android:name="android.car.application"
android:resource="@xml/automotive_app_desc" />
- <activity android:name=".MediaProxyActivity"
- android:theme="@android:style/Theme.NoTitleBar"
+ <activity android:name=".MediaActivity"
+ android:theme="@style/CarDrawerActivityTheme"
android:label="CarMediaApp"
android:resizeableActivity="true"
android:launchMode="singleTop">
diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java
index ab282dc4..3ff1d93c 100644
--- a/src/com/android/car/media/MediaActivity.java
+++ b/src/com/android/car/media/MediaActivity.java
@@ -16,21 +16,23 @@
package com.android.car.media;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.support.car.Car;
-import android.support.car.app.menu.CarDrawerActivity;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
+import com.android.car.app.CarDrawerActivity;
+import com.android.car.app.CarDrawerAdapter;
+import com.android.car.media.drawer.MediaDrawerController;
+
/**
* This activity controls the UI of media. It also updates the connection status for the media app
- * by broadcast. Drawer menu is controlled by {@link MediaCarMenuCallbacks}.
+ * by broadcast. Drawer menu is controlled by {@link MediaDrawerController}.
*/
public class MediaActivity extends CarDrawerActivity {
private static final String ACTION_MEDIA_APP_STATE_CHANGE
@@ -59,21 +61,17 @@ public class MediaActivity extends CarDrawerActivity {
*/
private boolean mContentFragmentChangeQueued;
+ private MediaDrawerController mDrawerController;
private View mScrimView;
private CrossfadeImageView mAlbumArtView;
private MediaPlaybackFragment mMediaPlaybackFragment;
- private MediaCarMenuCallbacks mMediaCarMenuCallbacks;
-
- public MediaActivity(Proxy proxy, Context context, Car car) {
- super(proxy, context, car);
- }
@Override
protected void onStart() {
super.onStart();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true);
- getContext().sendBroadcast(i);
+ sendBroadcast(i);
mIsStarted = true;
@@ -91,22 +89,21 @@ public class MediaActivity extends CarDrawerActivity {
super.onStop();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false);
- getContext().sendBroadcast(i);
+ sendBroadcast(i);
mIsStarted = false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
+ mDrawerController = new MediaDrawerController(this);
super.onCreate(savedInstanceState);
- setLightMode();
- mMediaCarMenuCallbacks = new MediaCarMenuCallbacks(this);
- setCarMenuCallbacks(mMediaCarMenuCallbacks);
- setContentView(R.layout.media_activity);
+
+ setMainContent(R.layout.media_activity);
mScrimView = findViewById(R.id.scrim);
mAlbumArtView = (CrossfadeImageView) findViewById(R.id.album_art);
- setBackgroundColor(getContext().getColor(R.color.music_default_artwork));
- MediaManager.getInstance(getContext()).addListener(mListener);
+ setBackgroundColor(getColor(R.color.music_default_artwork));
+ MediaManager.getInstance(this).addListener(mListener);
}
@Override
@@ -114,10 +111,15 @@ public class MediaActivity extends CarDrawerActivity {
super.onDestroy();
// Send the broadcast to let the current connected media app know it is disconnected now.
sendMediaConnectionStatusBroadcast(
- MediaManager.getInstance(getContext()).getCurrentComponent(),
+ MediaManager.getInstance(this).getCurrentComponent(),
MediaConstants.MEDIA_DISCONNECTED);
- mMediaCarMenuCallbacks.cleanup();
- MediaManager.getInstance(getContext()).removeListener(mListener);
+ mDrawerController.cleanup();
+ MediaManager.getInstance(this).removeListener(mListener);
+ }
+
+ @Override
+ protected CarDrawerAdapter getRootAdapter() {
+ return mDrawerController.getRootAdapter();
}
@Override
@@ -149,9 +151,7 @@ public class MediaActivity extends CarDrawerActivity {
}
setIntent(intent);
- if (isDrawerShowing()) {
- closeDrawer();
- }
+ closeDrawer();
}
@Override
@@ -230,19 +230,19 @@ public class MediaActivity extends CarDrawerActivity {
intent.getStringExtra(MediaManager.KEY_MEDIA_PACKAGE),
intent.getStringExtra(MediaManager.KEY_MEDIA_CLASS)
);
- MediaManager.getInstance(getContext()).setMediaClientComponent(component);
+ MediaManager.getInstance(this).setMediaClientComponent(component);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Launching most recent / default component.");
}
// Set it to the default GPM component.
- MediaManager.getInstance(getContext()).connectToMostRecentMediaComponent(
- new CarClientServiceAdapter(getContext().getPackageManager()));
+ MediaManager.getInstance(this).connectToMostRecentMediaComponent(
+ new CarClientServiceAdapter(getPackageManager()));
}
if (isSearchIntent(intent)) {
- MediaManager.getInstance(getContext()).processSearchIntent(intent);
+ MediaManager.getInstance(this).processSearchIntent(intent);
setIntent(null);
}
}
@@ -253,7 +253,7 @@ public class MediaActivity extends CarDrawerActivity {
}
private void sendMediaConnectionStatusBroadcast(
- ComponentName componentName, @Car.ConnectionType String connectionStatus) {
+ ComponentName componentName, String connectionStatus) {
// It will be no current component if no media app is chosen before.
if (componentName == null) {
return;
@@ -262,10 +262,10 @@ public class MediaActivity extends CarDrawerActivity {
Intent intent = new Intent(MediaConstants.ACTION_MEDIA_STATUS);
intent.setPackage(componentName.getPackageName());
intent.putExtra(MediaConstants.MEDIA_CONNECTION_STATUS, connectionStatus);
- getContext().sendBroadcast(intent);
+ sendBroadcast(intent);
}
- public void attachContentFragment() {
+ void attachContentFragment() {
if (mMediaPlaybackFragment == null) {
mMediaPlaybackFragment = new MediaPlaybackFragment();
}
@@ -298,4 +298,15 @@ public class MediaActivity extends CarDrawerActivity {
@Override
public void onStatusMessageChanged(String msg) {}
};
-}
+
+ private void setContentFragment(Fragment fragment) {
+ getSupportFragmentManager().beginTransaction()
+ .replace(getContentContainerId(), fragment)
+ .commit();
+ }
+
+
+ void showQueueInDrawer() {
+ mDrawerController.showQueueInDrawer();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/car/media/MediaCarMenuCallbacks.java b/src/com/android/car/media/MediaCarMenuCallbacks.java
deleted file mode 100644
index 26415ab9..00000000
--- a/src/com/android/car/media/MediaCarMenuCallbacks.java
+++ /dev/null
@@ -1,618 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.media.browse.MediaBrowser;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.car.app.menu.CarMenu;
-import android.support.car.app.menu.CarMenuCallbacks;
-import android.support.car.app.menu.RootMenu;
-import android.support.car.app.menu.compat.CarMenuConstantsComapt;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages all data needed for media drawer menu.
- */
-public class MediaCarMenuCallbacks extends CarMenuCallbacks {
-
- public static final String QUEUE_ROOT = "QUEUE_ROOT";
-
- private static final String TAG = "GH.MediaMenuCallbacks";
- // MEDIA_APP_ROOT is used for onGetRoot() of MediaMenuCallbacks, which is called so early that
- // MediaBrowser hasn't got the root already. So we return this default root first and store the
- // real one in mRootId.
- private static final String MEDIA_APP_ROOT = "MEDIA_APP_ROOT";
- private static final String EXTRA_ICON_SIZE =
- "com.google.android.gms.car.media.BrowserIconSize";
- private static final String QUEUE_ITEM_PREFIX = "queue_item_prefix_";
- private static final String MEDIA_QUEUE_EMPTY_PLACEHOLDER = "media_queue_emtpy_placeholder";
-
- private final MediaActivity mActivity;
- private final Context mContext;
- private final Handler mHandler;
- private MediaBrowser mBrowser;
- private MediaController mController;
- private CarMenu mMenuResult;
- private String mMediaId;
- private String mRootId;
- // The media id we want to subscribe but media browser is not connected at that time.
- private String mPendingMediaId;
- private long mActiveQueueItemId;
- private boolean mLoadQueueMenuPending;
- // Whether we add "Queue" as the last item in the main menu.
- private boolean mIsQueueInMenu;
- private List<MediaBrowser.MediaItem> mItems;
- private LoadQueueBitmapRunnable mLoadQueueBitmapRunnable;
- private LoadMenuBitmapRunnable mLoadMenuBitmapRunnable;
- // The parent ID is set whenever there's a onChildrenLoaded request.
- private UpdateMenuRunnable mUpdateMenuRunnable = new UpdateMenuRunnable();
-
- public MediaCarMenuCallbacks(MediaActivity activity) {
- mActivity = activity;
- mContext = activity.getContext();
- mHandler = new Handler();
- MediaManager.getInstance(mContext).addListener(mListener);
- }
-
- public void cleanup() {
- MediaManager.getInstance(mContext).removeListener(mListener);
- mHandler.removeCallbacksAndMessages(null);
- if (mBrowser != null) {
- if (mMediaId != null) {
- mBrowser.unsubscribe(mMediaId);
- mMediaId = null;
- }
- mBrowser.disconnect();
- mBrowser = null;
- }
- if (mController != null) {
- mController.unregisterCallback(mControllerCallback);
- mController = null;
- }
- }
-
- @Override
- public RootMenu onGetRoot(Bundle hints) {
- // Return the default fake root due to the real one maybe not ready at this time.
- return new RootMenu(MEDIA_APP_ROOT);
- }
-
- @Override
- public void onLoadChildren(String parentId, CarMenu result) {
- Log.d(TAG, "onLoadChildren " + parentId);
- resetCarMenu(result);
- if (QUEUE_ROOT.equals(parentId)) {
- // If mBrowser is not connected now, we will load the menu later when it is connected.
- if (mBrowser == null || !mBrowser.isConnected()) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "MediaBrowser is not connected while loading menu.");
- }
- mLoadQueueMenuPending = true;
- return;
- }
-
- // Unsubscribe the old id first, or else it will affect to subscribe the new one.
- if (!TextUtils.isEmpty(mMediaId) && !QUEUE_ROOT.equals(mMediaId)) {
- mBrowser.unsubscribe(mMediaId);
- }
- mMediaId = parentId;
-
- loadQueueMenu();
- } else {
- // If mBrowser is not connected now, we will load the menu later when it is connected.
- if (mBrowser == null || !mBrowser.isConnected()) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "MediaBrowser is not connected while loading menu.");
- }
- mPendingMediaId = parentId;
- return;
- }
-
- // Unsubscribe the old id first, or else it will affect to subscribe the new one.
- if (!TextUtils.isEmpty(mMediaId) && !QUEUE_ROOT.equals(mMediaId)) {
- mBrowser.unsubscribe(mMediaId);
- }
- // Replace the fake root id with the real one, then we can use it to subscribe.
- if (parentId.equals(MEDIA_APP_ROOT)) {
- mMediaId = mRootId;
- } else {
- mMediaId = parentId;
- }
- mBrowser.subscribe(mMediaId, mSubscriptionCallback);
- }
- }
-
- @Override
- public void onItemClicked(String id) {
- // We treat queue item specially because its id is different from the normal one.
- if (id.startsWith(QUEUE_ITEM_PREFIX)) {
- String index = id.substring(QUEUE_ITEM_PREFIX.length());
- mController.getTransportControls().skipToQueueItem(Long.valueOf(index));
- mActivity.closeDrawer();
- } else {
- if (mItems == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Media menu is empty.");
- }
- return;
- }
-
- for (MediaBrowser.MediaItem item : mItems) {
- if (item.getMediaId().equals(id)) {
- if (item.isPlayable()) {
- if (mController != null) {
- mController.getTransportControls().pause();
- mController.getTransportControls().playFromMediaId(item.getMediaId(),
- item.getDescription().getExtras());
- } else {
- Log.e(TAG, "MediaSession is destroyed.");
- }
- mActivity.closeDrawer();
- }
- break;
- }
- }
- }
- }
-
- private void resetCarMenu(CarMenu result) {
- // Stop loading previous menu due to we are under the new one now.
- if (mMenuResult != null) {
- if (mUpdateMenuRunnable != null) {
- mHandler.removeCallbacks(mUpdateMenuRunnable);
- // Spot fix. This runnable is being used in the subscription callbacks and is
- // causing a crash. The lifecycle here is a little messed up and needs to be
- // straightened out but for now just set it to a new object instead of setting
- // it to null.
- mUpdateMenuRunnable = new UpdateMenuRunnable();
- }
- if (mLoadMenuBitmapRunnable != null) {
- mHandler.removeCallbacks(mLoadMenuBitmapRunnable);
- mLoadMenuBitmapRunnable = null;
- }
- if (mLoadQueueBitmapRunnable != null) {
- mHandler.removeCallbacks(mLoadQueueBitmapRunnable);
- mLoadQueueBitmapRunnable = null;
- }
- }
- mMenuResult = result;
- mMenuResult.detach();
- }
-
- private CarMenu.Item emptyQueueMenu() {
- CarMenu.Builder builder = new CarMenu.Builder(MEDIA_QUEUE_EMPTY_PLACEHOLDER);
-
- final int iconColor = mContext.getResources().getColor(R.color.car_tint);
- Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_list_view_disable);
- drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
- builder.setIconFromSnapshot(drawable);
- builder.setIsEmptyPlaceHolder(true);
- return builder.build();
- }
-
- private void loadQueueMenu() {
- if (mMenuResult == null) {
- Log.w(TAG, "CarMenu is null while loading queue menu.");
- return;
- }
-
- List<CarMenu.Item> menuItems = new ArrayList<>();
- if (mController == null) {
- Log.w(TAG, "MediaController is null while loading queue menu.");
-
- // Add a icon for empty menu.
- sendEmptyMenu();
- } else {
- List<MediaSession.QueueItem> queue = mController.getQueue();
- mActiveQueueItemId = getActiveQueueItemId();
- boolean hasImages = false;
- for (MediaSession.QueueItem item : queue) {
- if ((item.getDescription().getIconUri() != null)
- || (item.getDescription().getIconBitmap() != null)) {
- hasImages = true;
- break;
- }
- }
- boolean activeQueueItemFound = false;
- for (final MediaSession.QueueItem item : queue) {
- // Only queue items following the active item are displayed in the menu.
- if (item.getQueueId() == mActiveQueueItemId) {
- activeQueueItemFound = true;
- }
-
- if (activeQueueItemFound) {
- CarMenu.Builder builder =
- new CarMenu.Builder(QUEUE_ITEM_PREFIX + item.getQueueId());
- builder.setTitle(item.getDescription().getTitle().toString())
- .setText(item.getDescription().getSubtitle().toString());
- // Place empty bitmap as place holder first, we will load the bitamp later.
- if (hasImages) {
- builder.setIcon(null);
- }
- if (item.getQueueId() == mActiveQueueItemId) {
- int primaryColor =
- MediaManager.getInstance(mContext).getMediaClientPrimaryColor();
- Drawable drawable =
- mContext.getResources().getDrawable(R.drawable.ic_music_active);
- drawable.setColorFilter(primaryColor, PorterDuff.Mode.SRC_IN);
- builder.setRightIconFromSnapshot(drawable);
- }
- menuItems.add(builder.build());
- }
- }
-
- // If we have not found any items then set the menu to empty placeholder item.
- if (menuItems.size() == 0) {
- sendEmptyMenu();
- } else {
- mMenuResult.sendResult(menuItems);
- mMenuResult = null;
- }
-
- if (hasImages) {
- if (mLoadQueueBitmapRunnable != null) {
- mHandler.removeCallbacks(mLoadQueueBitmapRunnable);
- }
- mLoadQueueBitmapRunnable = new LoadQueueBitmapRunnable(queue, QUEUE_ROOT);
- mHandler.post(mLoadQueueBitmapRunnable);
- }
- }
- }
-
- private void sendEmptyMenu() {
- if (mMenuResult != null) {
- List<CarMenu.Item> menuItems = new ArrayList<CarMenu.Item>();
- menuItems.add(emptyQueueMenu());
- mMenuResult.sendResult(menuItems);
- mMenuResult = null;
- }
- }
-
- private boolean enableQueueItem(List<MediaSession.QueueItem> items) {
- if (items == null || mController == null) {
- return false;
- }
-
- if (mIsQueueInMenu) {
- // We already have a queue item; do nothing
- return false;
- }
- if (TextUtils.isEmpty(mController.getQueueTitle())) {
- // No queue title to show; do nothing
- return false;
- }
- return true;
- }
-
- private long getActiveQueueItemId() {
- if (mController == null) {
- return MediaSession.QueueItem.UNKNOWN_ID;
- }
-
- PlaybackState playbackState = mController.getPlaybackState();
- if (playbackState != null) {
- return playbackState.getActiveQueueItemId();
- } else {
- return MediaSession.QueueItem.UNKNOWN_ID;
- }
- }
-
- private final MediaManager.Listener mListener = new MediaManager.Listener() {
-
- @Override
- public void onMediaAppChanged(ComponentName componentName) {
- mRootId = null;
- if (mBrowser != null) {
- // Unsubscribe the old id first, or else it will affect to subscribe the new one.
- if (!TextUtils.isEmpty(mMediaId) && !QUEUE_ROOT.equals(mMediaId)) {
- mBrowser.unsubscribe(mMediaId);
- mMediaId = null;
- }
- mBrowser.disconnect();
- mBrowser = null;
- }
- Resources resources = mContext.getResources();
- Bundle extras = new Bundle();
- if (resources != null) {
- extras.putInt(EXTRA_ICON_SIZE,
- resources.getDimensionPixelSize(R.dimen.car_list_item_icon_size));
- }
- mBrowser = new MediaBrowser(mContext, componentName, mConnectionCallbacks, extras);
- if (mController != null) {
- mController.unregisterCallback(mControllerCallback);
- mController = null;
- }
- mBrowser.connect();
- // Only store MediaManager instance to a local variable when it is short lived.
- MediaManager mediaManager = MediaManager.getInstance(mContext);
- mActivity.setTitle(mediaManager.getMediaClientName().toString());
- mActivity.setScrimColor(mediaManager.getMediaClientPrimaryColorDark());
- mActivity.attachContentFragment();
- }
-
- @Override
- public void onStatusMessageChanged(String msg) {}
- };
-
- private final MediaBrowser.ConnectionCallback mConnectionCallbacks =
- new MediaBrowser.ConnectionCallback() {
-
- @Override
- public void onConnected() {
- // Get the real root and will replace it with the default fake one which is set
- // in onGetRoot().
- mRootId = mBrowser.getRoot();
- if (mPendingMediaId != null) {
- mMediaId = mPendingMediaId.equals(MEDIA_APP_ROOT) ? mRootId : mPendingMediaId;
- mPendingMediaId = null;
- } else {
- mMediaId = mRootId;
- }
- MediaSession.Token token = mBrowser.getSessionToken();
- if (token != null) {
- mController = new MediaController(mContext, token);
- mController.registerCallback(mControllerCallback);
- } else {
- // We will still be able to browse media content, but not able to play them.
- Log.e(TAG, "Media session token is null for "
- + MediaManager.getInstance(mContext).getMediaClientName());
- }
- if (mLoadQueueMenuPending) {
- mLoadQueueMenuPending = false;
- loadQueueMenu();
- } else {
- mBrowser.subscribe(mMediaId, mSubscriptionCallback);
- }
- }
-
- @Override
- public void onConnectionSuspended() {
- Log.w(TAG, "Media browser service connection suspended. Waiting to be"
- + " reconnected....");
- }
-
- @Override
- public void onConnectionFailed() {
- Log.e(TAG, "Media browser service connection FAILED!");
- sendEmptyMenu();
- // disconnect anyway to make sure we get into a sanity state
- mBrowser.disconnect();
- mBrowser = null;
- }
- };
-
- private final MediaController.Callback mControllerCallback = new MediaController.Callback() {
-
- @Override
- public void onSessionDestroyed() {
- Log.e(TAG, "Media session is destroyed");
- sendEmptyMenu();
- if (mController != null) {
- mController.unregisterCallback(mControllerCallback);
- }
- mController = null;
- }
-
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- long activeQueueItemId = getActiveQueueItemId();
- if (mActiveQueueItemId != activeQueueItemId) {
- if (mMediaId == QUEUE_ROOT) {
- // After this call, the whole queue menu will be refreshed.
- notifyChildrenChanged(QUEUE_ROOT);
- }
- mActiveQueueItemId = activeQueueItemId;
- }
- }
-
- @Override
- public void onQueueChanged(List<MediaSession.QueueItem> queue) {
- if (mMediaId == mRootId && enableQueueItem(queue)) {
- notifyChildrenChanged(MEDIA_APP_ROOT);
- }
- }
- };
-
- private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
- new MediaBrowser.SubscriptionCallback() {
-
- @Override
- public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
- Log.d(TAG, "onChildrenLoaded" + parentId);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Loaded " + children.size() + " children.");
- for (MediaBrowser.MediaItem item : children) {
- Log.d(TAG, "\t" + item.getDescription().getTitle());
- }
- }
-
- mIsQueueInMenu = false;
- if (mController == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "MediaController is null in SubscriptionCallback.");
- }
- sendEmptyMenu();
- // the session has been destroyed or we have moved to another facet.
- return;
- }
-
- mItems = new ArrayList<>(children);
- mHandler.removeCallbacks(mUpdateMenuRunnable);
- mUpdateMenuRunnable.setParentId(parentId);
- mHandler.post(mUpdateMenuRunnable);
- }
-
- @Override
- public void onError(String mediaId) {
- Log.e(TAG, "onError getting items for " + mediaId);
- sendEmptyMenu();
- }
- };
-
- private class UpdateMenuRunnable implements Runnable {
- private String mParentId;
-
- void setParentId(String parentId) {
- mParentId = parentId;
- }
-
- @Override
- public void run() {
- if (mMenuResult == null) {
- Log.e(TAG, "CarMenu is null while update menu, notify change instead.");
- notifyChildrenChanged(mParentId);
- return;
- }
-
- if (mItems == null) {
- throw new IllegalArgumentException(
- "You must supply CarMenu with a list of MediaItems.");
- }
-
- boolean hasImages = false;
- for (MediaBrowser.MediaItem item : mItems) {
- if ((item.getDescription().getIconUri() != null)
- || (item.getDescription().getIconBitmap() != null)) {
- hasImages = true;
- break;
- }
- }
- List<CarMenu.Item> menuItems = new ArrayList<>();
- for (MediaBrowser.MediaItem item : mItems) {
- menuItems.add(convertMediaItemToMenuItem(item, hasImages));
- }
- // If it is under root menu and play queue is not empty, add "Queue" item to the menu.
- if (mMediaId.equals(mRootId) && mController != null) {
- List<MediaSession.QueueItem> queue = mController.getQueue();
- if (queue != null && queue.size() > 0
- && !TextUtils.isEmpty(mController.getQueueTitle())) {
- String queueTitle = mController.getQueueTitle().toString();
- menuItems.add(new CarMenu.Builder(QUEUE_ROOT).setTitle(queueTitle)
- .setFlags(CarMenuConstantsComapt.MenuItemConstants.FLAG_BROWSABLE)
- .build());
- mIsQueueInMenu = true;
- }
- }
- if (menuItems.size() == 0) {
- sendEmptyMenu();
- } else {
- mMenuResult.sendResult(menuItems);
- mMenuResult = null;
- }
-
- if (hasImages) {
- if (mLoadMenuBitmapRunnable != null) {
- mHandler.removeCallbacks(mLoadMenuBitmapRunnable);
- }
- // Due to we return fake root id in onGetRoot(), when we call notifyChildChanged()
- // we still need to use the fake root id instead of the real one.
- if (mMediaId.equals(mRootId)) {
- mLoadMenuBitmapRunnable = new LoadMenuBitmapRunnable(mItems, MEDIA_APP_ROOT);
- } else {
- mLoadMenuBitmapRunnable = new LoadMenuBitmapRunnable(mItems, mMediaId);
- }
- mHandler.post(mLoadMenuBitmapRunnable);
- }
- }
-
- /**
- * Returns CarMenu.Item which is used in rendering menu.
- *
- * @param item MediaItem which has all info to render menu.
- * @param hasImages Whether the menu item has image or not.
- * @return menu item.
- */
- private CarMenu.Item convertMediaItemToMenuItem(MediaBrowser.MediaItem item,
- boolean hasImages) {
- CarMenu.Builder builder = new CarMenu.Builder(item.getMediaId());
- CharSequence title = item.getDescription().getTitle();
- if (title != null) {
- builder.setTitle(title.toString());
- }
- CharSequence subTitle = item.getDescription().getSubtitle();
- if (subTitle != null) {
- builder.setText(subTitle.toString());
- }
- if (item.isBrowsable()) {
- builder.setFlags(CarMenuConstantsComapt.MenuItemConstants.FLAG_BROWSABLE);
- }
- // Place empty bitmap as place holder first, we will load the bitamp later.
- if (hasImages) {
- builder.setIcon(null);
- }
- return builder.build();
- }
- }
-
- private class LoadQueueBitmapRunnable implements Runnable {
- private final List<MediaSession.QueueItem> mQueue;
- private final String mParentId;
-
- public LoadQueueBitmapRunnable(List<MediaSession.QueueItem> queue, String parentId) {
- mQueue = queue;
- mParentId = parentId;
- }
-
- @Override
- public void run() {
- boolean activeQueueItemFound = false;
- for (MediaSession.QueueItem item : mQueue) {
- if (item.getQueueId() == mActiveQueueItemId) {
- activeQueueItemFound = true;
- }
-
- if (activeQueueItemFound) {
- MediaMenuBitmapDownloader downloader = new MediaMenuBitmapDownloader(mContext,
- MediaCarMenuCallbacks.this, mParentId,
- QUEUE_ITEM_PREFIX + item.getQueueId(), mHandler);
- downloader.setMenuBitmap(item.getDescription());
- }
- }
- }
- }
-
- private class LoadMenuBitmapRunnable implements Runnable {
- private List<MediaBrowser.MediaItem> mItemList;
- private String mParentId;
-
- public LoadMenuBitmapRunnable(List<MediaBrowser.MediaItem> itemList, String parentId) {
- mItemList = itemList;
- mParentId = parentId;
- }
-
- @Override
- public void run() {
- for (MediaBrowser.MediaItem item : mItemList) {
- MediaMenuBitmapDownloader downloader = new MediaMenuBitmapDownloader(mContext,
- MediaCarMenuCallbacks.this, mParentId, item.getMediaId(), mHandler);
- downloader.setMenuBitmap(item.getDescription());
- }
- }
- }
-}
diff --git a/src/com/android/car/media/MediaMenuBitmapDownloader.java b/src/com/android/car/media/MediaMenuBitmapDownloader.java
deleted file mode 100644
index ea8e3704..00000000
--- a/src/com/android/car/media/MediaMenuBitmapDownloader.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.media.MediaDescription;
-import android.net.Uri;
-import android.os.Handler;
-import android.support.car.app.menu.CarMenu;
-import android.util.Log;
-import com.android.car.apps.common.BitmapDownloader;
-import com.android.car.apps.common.BitmapWorkerOptions;
-import com.android.car.apps.common.UriUtils;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Download the icon for car menu item. Once it is done, it will update the icon by calling
- * CarMenuCallbacks.notifyChildChanged(), which is needed to be called after CarMenu.sendResult().
- */
-public class MediaMenuBitmapDownloader {
- private static final String TAG = "GH.MBDownloader";
- private static final int MAX_ALBUM_ART_DOWNLOAD_RETRIES = 10;
- private static final long ALBUM_ART_DOWNLOAD_RETRY_INTERVAL_MS = 1000;
-
- private final WeakReference<Context> mContext;
- private final MediaCarMenuCallbacks mCallback;
- private final String mParentId;
- private final String mChildId;
- private final Handler mHandler;
- private BitmapDownloadRunnable mBitmapDownloadRunnable;
-
- public MediaMenuBitmapDownloader(Context context, MediaCarMenuCallbacks callback,
- String parentId, String childId, Handler handler) {
- mContext = new WeakReference<>(context);
- mCallback = callback;
- mParentId = parentId;
- mChildId = childId;
- mHandler = handler;
- }
-
- public void setMenuBitmap(MediaDescription description) {
- if (description == null) {
- Log.w(TAG, "null media descriptor");
- return;
- }
-
- if (mBitmapDownloadRunnable != null) {
- mHandler.removeCallbacks(mBitmapDownloadRunnable);
- mBitmapDownloadRunnable.cancelDownload();
- mBitmapDownloadRunnable = null;
- }
-
- Bitmap bitmap = description.getIconBitmap();
- Uri iconUri = description.getIconUri();
- if (bitmap == null && iconUri == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "no bitmap or icon uri found");
- }
- } else if (bitmap != null) {
- updateIcon(bitmap);
- } else {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "downloading bitmap " + iconUri);
- }
- mBitmapDownloadRunnable = new BitmapDownloadRunnable(iconUri);
- mHandler.post(mBitmapDownloadRunnable);
- }
- }
-
- private void updateIcon(Bitmap bitmap) {
- mCallback.notifyChildChanged(mParentId,
- new CarMenu.Builder(mChildId).setIcon(bitmap).build());
- }
-
- private class BitmapDownloadRunnable implements Runnable {
- private Uri mIconUri;
- private int mRetries;
- private BitmapDownloader.BitmapCallback mBitmapCallback;
-
- public BitmapDownloadRunnable(Uri icon_uri) {
- mIconUri = icon_uri;
- mRetries = 0;
- }
-
- public void cancelDownload() {
- if (mBitmapCallback != null) {
- Context context = mContext.get();
- if (context == null) {
- return;
- }
-
- BitmapDownloader.getInstance(context).cancelDownload(mBitmapCallback);
- }
- }
-
- @Override
- public void run() {
- mBitmapCallback = new BitmapDownloader.BitmapCallback() {
- @Override
- public void onBitmapRetrieved(Bitmap bitmap) {
- if (bitmap == null) {
- if (++mRetries <= MAX_ALBUM_ART_DOWNLOAD_RETRIES) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "retrying after failing to download bitmap "
- + mIconUri.toString());
- }
- mHandler.postDelayed(BitmapDownloadRunnable.this,
- ALBUM_ART_DOWNLOAD_RETRY_INTERVAL_MS);
- }
- } else {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "downloaded bitmap " + mIconUri.toString() + " retries:"
- + mRetries);
- }
- updateIcon(bitmap);
- }
- }
- };
-
- Context context = mContext.get();
- if (context == null) {
- return;
- }
-
- int bitmapSize =
- context.getResources().getDimensionPixelSize(R.dimen.car_list_item_icon_size);
- BitmapDownloader.getInstance(context)
- .getBitmap(
- new BitmapWorkerOptions.Builder(context).resource(mIconUri)
- .height(bitmapSize)
- .width(bitmapSize)
- // We don't want to cache android resources as they are needed
- // to be refreshed after configuration changes.
- .cacheFlag(UriUtils.isAndroidResourceUri(mIconUri)
- ? (BitmapWorkerOptions.CACHE_FLAG_DISK_DISABLED
- | BitmapWorkerOptions.CACHE_FLAG_MEM_DISABLED)
- : 0)
- .build(),
- mBitmapCallback);
- }
- }
-}
diff --git a/src/com/android/car/media/MediaPlaybackFragment.java b/src/com/android/car/media/MediaPlaybackFragment.java
index 04bd8469..8bd44f1a 100644
--- a/src/com/android/car/media/MediaPlaybackFragment.java
+++ b/src/com/android/car/media/MediaPlaybackFragment.java
@@ -144,11 +144,10 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
mActivity = (MediaActivity) getHost();
mShowTitleDelayMs =
mActivity.getResources().getInteger(R.integer.new_album_art_fade_in_offset);
- mMediaPlaybackModel =
- new MediaPlaybackModel(mActivity.getContext(), null /* browserExtras */);
+ mMediaPlaybackModel = new MediaPlaybackModel(mActivity, null /* browserExtras */);
mMediaPlaybackModel.addListener(this);
- mTelephonyManager = (TelephonyManager) mActivity.getContext()
- .getSystemService(Context.TELEPHONY_SERVICE);
+ mTelephonyManager =
+ (TelephonyManager) mActivity.getSystemService(Context.TELEPHONY_SERVICE);
}
@Override
@@ -156,7 +155,6 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
super.onDestroy();
mCurrentView = null;
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- mMediaPlaybackModel.onDestroy();
mMediaPlaybackModel = null;
mActivity = null;
// Calling this with null will clear queue of callbacks and message.
@@ -237,7 +235,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
@Override
public void onPause() {
super.onPause();
- mMediaPlaybackModel.onPause();
+ mMediaPlaybackModel.stop();
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
@@ -252,7 +250,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
@Override
public void onResume() {
super.onResume();
- mMediaPlaybackModel.onResume();
+ mMediaPlaybackModel.start();
// Note: at registration, TelephonyManager will invoke the callback with the current state.
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
@@ -271,8 +269,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
int overflowViewColor = mMediaPlaybackModel.getPrimaryColorDark();
mOverflowView.getBackground().setColorFilter(overflowViewColor, PorterDuff.Mode.SRC_IN);
// Tint the overflow actions light or dark depending on contrast.
- int overflowTintColor = ColorChecker.getTintColor(
- mActivity.getContext(), overflowViewColor);
+ int overflowTintColor = ColorChecker.getTintColor(mActivity, overflowViewColor);
for (ImageView v : mCustomActionButtons) {
v.setColorFilter(overflowTintColor, PorterDuff.Mode.SRC_IN);
}
@@ -335,7 +332,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
}
showInitialNoContentView(state.getErrorMessage() != null ?
state.getErrorMessage().toString() :
- mActivity.getContext().getString(R.string.unknown_error), true);
+ mActivity.getString(R.string.unknown_error), true);
return;
}
@@ -400,7 +397,6 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
icon == null || mReturnFromOnStop ? 0 : mShowTitleDelayMs);
}
Uri iconUri = getMetadataIconUri(metadata);
- Context context = mActivity.getContext();
if (icon != null) {
Bitmap scaledIcon = cropAlbumArt(icon);
if (scaledIcon != icon && !icon.isRecycled()) {
@@ -412,7 +408,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
mActivity.setBackgroundBitmap(scaledIcon, !mReturnFromOnStop /* showAnimation */);
} else if (iconUri != null) {
if (mDownloader == null) {
- mDownloader = new BitmapDownloader(context);
+ mDownloader = new BitmapDownloader(mActivity);
}
final int flags = BitmapWorkerOptions.CACHE_FLAG_DISK_DISABLED
| BitmapWorkerOptions.CACHE_FLAG_MEM_DISABLED;
@@ -420,7 +416,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
Log.v(TAG, "Album art size " + mAlbumArtWidth + "x" + mAlbumArtHeight);
}
- mDownloader.getBitmap(new BitmapWorkerOptions.Builder(context).resource(iconUri)
+ mDownloader.getBitmap(new BitmapWorkerOptions.Builder(mActivity).resource(iconUri)
.height(mAlbumArtHeight).width(mAlbumArtWidth).cacheFlag(flags).build(),
new BitmapDownloader.BitmapCallback() {
@Override
@@ -505,7 +501,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
}
});
- int tint = ColorChecker.getTintColor(mActivity.getContext(),
+ int tint = ColorChecker.getTintColor(mActivity,
mMediaPlaybackModel.getPrimaryColorDark());
mSeekBar.getProgressDrawable().setColorFilter(tint, PorterDuff.Mode.SRC_IN);
} else {
@@ -870,9 +866,7 @@ public class MediaPlaybackFragment extends Fragment implements MediaPlaybackMode
} else {
switch (v.getId()) {
case R.id.play_queue:
- CharSequence title = mMediaPlaybackModel.getQueueTitle();
- mActivity.showMenu(MediaCarMenuCallbacks.QUEUE_ROOT, title.toString());
- mActivity.openDrawer();
+ mActivity.showQueueInDrawer();
break;
case R.id.prev:
transportControls.skipToPrevious();
diff --git a/src/com/android/car/media/MediaPlaybackModel.java b/src/com/android/car/media/MediaPlaybackModel.java
index 6f234e2a..03a88177 100644
--- a/src/com/android/car/media/MediaPlaybackModel.java
+++ b/src/com/android/car/media/MediaPlaybackModel.java
@@ -46,7 +46,7 @@ import java.util.function.Consumer;
* main thread. Intended to provide a much more usable model interface to UI code.
*/
public class MediaPlaybackModel {
- private static final String TAG = "GH.MediaPlaybackModel";
+ private static final String TAG = "MediaPlaybackModel";
private final Context mContext;
private final Bundle mBrowserExtras;
@@ -55,6 +55,7 @@ public class MediaPlaybackModel {
private Handler mHandler;
private MediaController mController;
private MediaBrowser mBrowser;
+ private int mPrimaryColor;
private int mPrimaryColorDark;
private int mAccentColor;
private ComponentName mCurrentComponentName;
@@ -94,6 +95,29 @@ public class MediaPlaybackModel {
void onSessionDestroyed(CharSequence destroyedMediaClientName);
}
+ /** Convenient Listener base class for extension */
+ public static abstract class AbstractListener implements Listener {
+ @Override
+ public void onMediaAppChanged(@Nullable ComponentName currentName,
+ @Nullable ComponentName newName) {}
+ @Override
+ public void onMediaAppStatusMessageChanged(@Nullable String message) {}
+ @Override
+ public void onMediaConnected() {}
+ @Override
+ public void onMediaConnectionSuspended() {}
+ @Override
+ public void onMediaConnectionFailed(CharSequence failedMediaClientName) {}
+ @Override
+ public void onPlaybackStateChanged(@Nullable PlaybackState state) {}
+ @Override
+ public void onMetadataChanged(@Nullable MediaMetadata metadata) {}
+ @Override
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {}
+ @Override
+ public void onSessionDestroyed(CharSequence destroyedMediaClientName) {}
+ }
+
public MediaPlaybackModel(Context context, Bundle browserExtras) {
mContext = context;
mBrowserExtras = browserExtras;
@@ -101,13 +125,13 @@ public class MediaPlaybackModel {
}
@MainThread
- public void onDestroy() {
+ public void start() {
Assert.isMainThread();
- mHandler = null;
+ MediaManager.getInstance(mContext).addListener(mMediaManagerListener);
}
@MainThread
- public void onPause() {
+ public void stop() {
Assert.isMainThread();
MediaManager.getInstance(mContext).removeListener(mMediaManagerListener);
if (mBrowser != null) {
@@ -126,12 +150,6 @@ public class MediaPlaybackModel {
}
@MainThread
- public void onResume() {
- Assert.isMainThread();
- MediaManager.getInstance(mContext).addListener(mMediaManagerListener);
- }
-
- @MainThread
public void addListener(MediaPlaybackModel.Listener listener) {
Assert.isMainThread();
mListeners.add(listener);
@@ -146,8 +164,11 @@ public class MediaPlaybackModel {
@MainThread
private void notifyListeners(Consumer<Listener> callback) {
Assert.isMainThread();
+ // Clone mListeners in case any of the callbacks made triggers a listener to be added or
+ // removed to/from mListeners.
+ List<Listener> listenersCopy = new LinkedList<>(mListeners);
// Invokes callback.accept(listener) for each listener.
- mListeners.forEach(callback);
+ listenersCopy.forEach(callback);
}
@MainThread
@@ -157,6 +178,12 @@ public class MediaPlaybackModel {
}
@MainThread
+ public int getPrimaryColor() {
+ Assert.isMainThread();
+ return mPrimaryColor;
+ }
+
+ @MainThread
public int getAccentColor() {
Assert.isMainThread();
return mAccentColor;
@@ -227,6 +254,12 @@ public class MediaPlaybackModel {
}
@MainThread
+ public MediaBrowser getMediaBrowser() {
+ Assert.isMainThread();
+ return mBrowser;
+ }
+
+ @MainThread
public MediaController.TransportControls getTransportControls() {
Assert.isMainThread();
if (mController == null) {
@@ -266,10 +299,10 @@ public class MediaPlaybackModel {
mBrowser.connect();
// reset the colors and views if we switch to another app.
- mAccentColor = MediaManager.getInstance(mContext)
- .getMediaClientAccentColor();
- mPrimaryColorDark = MediaManager.getInstance(mContext)
- .getMediaClientPrimaryColorDark();
+ MediaManager manager = MediaManager.getInstance(mContext);
+ mPrimaryColor = manager.getMediaClientPrimaryColor();
+ mAccentColor = manager.getMediaClientAccentColor();
+ mPrimaryColorDark = manager.getMediaClientPrimaryColorDark();
final ComponentName currentName = mCurrentComponentName;
notifyListeners((listener) -> listener.onMediaAppChanged(currentName, name));
diff --git a/src/com/android/car/media/MediaProxyActivity.java b/src/com/android/car/media/MediaProxyActivity.java
deleted file mode 100644
index c1950db2..00000000
--- a/src/com/android/car/media/MediaProxyActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.support.car.app.CarProxyActivity;
-
-
-public class MediaProxyActivity extends CarProxyActivity {
-
- public MediaProxyActivity() {
- super(MediaActivity.class);
- }
-}
diff --git a/src/com/android/car/media/drawer/MediaBrowserItemsFetcher.java b/src/com/android/car/media/drawer/MediaBrowserItemsFetcher.java
new file mode 100644
index 00000000..80bc5d73
--- /dev/null
+++ b/src/com/android/car/media/drawer/MediaBrowserItemsFetcher.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2017 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.drawer;
+
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.util.Log;
+
+import com.android.car.app.CarDrawerActivity;
+import com.android.car.app.DrawerItemViewHolder;
+import com.android.car.media.MediaPlaybackModel;
+import com.android.car.media.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link MediaItemsFetcher} implementation that fetches items from a specific {@link MediaBrowser}
+ * node.
+ * <p>
+ * It optionally supports surfacing the Media app's queue as the last item.
+ */
+class MediaBrowserItemsFetcher implements MediaItemsFetcher {
+ private static final String TAG = "Media.BrowserFetcher";
+
+ private final CarDrawerActivity mActivity;
+ private final MediaPlaybackModel mMediaPlaybackModel;
+ private final String mMediaId;
+ private final boolean mShowQueueItem;
+ private ItemsUpdatedCallback mCallback;
+ private List<MediaBrowser.MediaItem> mItems = new ArrayList<>();
+ private boolean mQueueAvailable;
+
+ MediaBrowserItemsFetcher(CarDrawerActivity activity, MediaPlaybackModel model, String mediaId,
+ boolean showQueueItem) {
+ mActivity = activity;
+ mMediaPlaybackModel = model;
+ mMediaId = mediaId;
+ mShowQueueItem = showQueueItem;
+ }
+
+ @Override
+ public void start(ItemsUpdatedCallback callback) {
+ mCallback = callback;
+ updateQueueAvailability();
+ mMediaPlaybackModel.getMediaBrowser().subscribe(mMediaId, mSubscriptionCallback);
+ mMediaPlaybackModel.addListener(mModelListener);
+ }
+
+ private final MediaPlaybackModel.Listener mModelListener =
+ new MediaPlaybackModel.AbstractListener() {
+ @Override
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+ updateQueueAvailability();
+ }
+ @Override
+ public void onSessionDestroyed(CharSequence destroyedMediaClientName) {
+ updateQueueAvailability();
+ }
+ };
+
+ private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+ new MediaBrowser.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+ mItems.clear();
+ mItems.addAll(children);
+ mCallback.onItemsUpdated();
+ }
+
+ @Override
+ public void onError(String parentId) {
+ Log.e(TAG, "Error loading children of: " + mMediaId);
+ mItems.clear();
+ mCallback.onItemsUpdated();
+ }
+ };
+
+ private void updateQueueAvailability() {
+ if (mShowQueueItem && !mMediaPlaybackModel.getQueue().isEmpty()) {
+ mQueueAvailable = true;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ int size = mItems.size();
+ if (mQueueAvailable) {
+ size++;
+ }
+ return size;
+ }
+
+ @Override
+ public void populateViewHolder(DrawerItemViewHolder holder, int position) {
+ if (mQueueAvailable && position == mItems.size()) {
+ holder.getTitle().setText(mMediaPlaybackModel.getQueueTitle());
+ return;
+ }
+ MediaBrowser.MediaItem item = mItems.get(position);
+ MediaItemsFetcher.populateViewHolderFrom(holder, item.getDescription());
+
+ // TODO(sriniv): Once we use smallLayout, text and rightIcon fields may be unavailable.
+ // Related to b/36573125.
+ if (item.isBrowsable()) {
+ int iconColor = mActivity.getColor(R.color.car_tint);
+ Drawable drawable = mActivity.getDrawable(R.drawable.ic_chevron_right);
+ drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
+ holder.getRightIcon().setImageDrawable(drawable);
+ } else {
+ holder.getRightIcon().setImageDrawable(null);
+ }
+ }
+
+ @Override
+ public void onItemClick(int position) {
+ if (mQueueAvailable && position == mItems.size()) {
+ MediaItemsFetcher fetcher = new MediaQueueItemsFetcher(mActivity, mMediaPlaybackModel);
+ setupAdapterAndSwitch(fetcher, mMediaPlaybackModel.getQueueTitle());
+ return;
+ }
+
+ MediaBrowser.MediaItem item = mItems.get(position);
+ if (item.isBrowsable()) {
+ MediaItemsFetcher fetcher = new MediaBrowserItemsFetcher(
+ mActivity, mMediaPlaybackModel, item.getMediaId(), false /* showQueueItem */);
+ setupAdapterAndSwitch(fetcher, item.getDescription().getTitle());
+ } else if (item.isPlayable()) {
+ MediaController.TransportControls controls = mMediaPlaybackModel.getTransportControls();
+ if (controls != null) {
+ controls.pause();
+ controls.playFromMediaId(item.getMediaId(), item.getDescription().getExtras());
+ }
+ mActivity.closeDrawer();
+ } else {
+ Log.w(TAG, "Unknown item type; don't know how to handle!");
+ }
+ }
+
+ private void setupAdapterAndSwitch(MediaItemsFetcher fetcher, CharSequence title) {
+ MediaDrawerAdapter subAdapter = new MediaDrawerAdapter(mActivity, false /* smallLayout */);
+ subAdapter.setFetcher(fetcher);
+ subAdapter.setTitle(title);
+ mActivity.switchToAdapter(subAdapter);
+ }
+
+ @Override
+ public void cleanup() {
+ mMediaPlaybackModel.removeListener(mModelListener);
+ mMediaPlaybackModel.getMediaBrowser().unsubscribe(mMediaId);
+ mCallback = null;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/car/media/drawer/MediaDrawerAdapter.java b/src/com/android/car/media/drawer/MediaDrawerAdapter.java
new file mode 100644
index 00000000..dc483a2b
--- /dev/null
+++ b/src/com/android/car/media/drawer/MediaDrawerAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.drawer;
+
+import com.android.car.app.CarDrawerActivity;
+import com.android.car.app.CarDrawerAdapter;
+import com.android.car.app.DrawerItemViewHolder;
+
+/**
+ * Subclass of CarDrawerAdapter used by the Media app.
+ * <p>
+ * This adapter delegates actual fetching of items (and other operations) to a
+ * {@link MediaItemsFetcher}. The current fetcher being used can be updated at runtime.
+ */
+class MediaDrawerAdapter extends CarDrawerAdapter {
+ private final CarDrawerActivity mActivity;
+ private MediaItemsFetcher mCurrentFetcher;
+
+ MediaDrawerAdapter(CarDrawerActivity activity, boolean useSmallLayout) {
+ super(activity, true /* showDisabledListOnEmpty */, useSmallLayout);
+ mActivity = activity;
+ }
+
+ /**
+ * Switch the {@link MediaItemsFetcher} being used to fetch items. The new fetcher is kicked-off
+ * and the drawer's content's will be updated to show newly loaded items. Any old fetcher is
+ * cleaned up and released.
+ *
+ * @param fetcher New {@link MediaItemsFetcher} to use for display Drawer items.
+ */
+ void setFetcher(MediaItemsFetcher fetcher) {
+ if (mCurrentFetcher != null) {
+ mCurrentFetcher.cleanup();
+ }
+ mCurrentFetcher = fetcher;
+ mCurrentFetcher.start(() -> {
+ mActivity.showLoadingProgressBar(false);
+ notifyDataSetChanged();
+ });
+ // Initially there will be no items and we don't want to show empty-list indicator briefly
+ // until items are fetched.
+ mActivity.showLoadingProgressBar(true);
+ }
+
+ @Override
+ protected int getActualItemCount() {
+ return mCurrentFetcher != null ? mCurrentFetcher.getItemCount() : 0;
+ }
+
+ @Override
+ protected void populateViewHolder(DrawerItemViewHolder holder, int position) {
+ if (mCurrentFetcher != null) {
+ mCurrentFetcher.populateViewHolder(holder, position);
+ }
+ }
+
+ @Override
+ public void onItemClick(int position) {
+ if (mCurrentFetcher != null) {
+ mCurrentFetcher.onItemClick(position);
+ }
+ }
+}
diff --git a/src/com/android/car/media/drawer/MediaDrawerController.java b/src/com/android/car/media/drawer/MediaDrawerController.java
new file mode 100644
index 00000000..e1715bde
--- /dev/null
+++ b/src/com/android/car/media/drawer/MediaDrawerController.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.drawer;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+
+import com.android.car.app.CarDrawerActivity;
+import com.android.car.app.CarDrawerAdapter;
+import com.android.car.media.MediaManager;
+import com.android.car.media.MediaPlaybackModel;
+import com.android.car.media.R;
+
+/**
+ * Manages overall Drawer functionality.
+ * <p>
+ * Maintains separate MediaPlaybackModel for media browsing and control. Sets up root Drawer
+ * adapter with root of media-browse tree (using MediaBrowserItemsFetcher). Supports switching the
+ * rootAdapter to show the queue-items (using MediaQueueItemsFetcher).
+ */
+public class MediaDrawerController {
+ private static final String TAG = "MediaDrawerController";
+ private static final String EXTRA_ICON_SIZE =
+ "com.google.android.gms.car.media.BrowserIconSize";
+
+ private final CarDrawerActivity mActivity;
+ private final MediaPlaybackModel mMediaPlaybackModel;
+ private MediaDrawerAdapter mRootAdapter;
+
+ public MediaDrawerController(CarDrawerActivity activity) {
+ mActivity = activity;
+ Bundle extras = new Bundle();
+ extras.putInt(EXTRA_ICON_SIZE,
+ mActivity.getResources().getDimensionPixelSize(R.dimen.car_list_item_icon_size));
+ mMediaPlaybackModel = new MediaPlaybackModel(mActivity, extras);
+ mMediaPlaybackModel.addListener(mModelListener);
+
+ // TODO(sriniv): Needs smallLayout below. But breaks when showing queue items (b/36573125).
+ mRootAdapter = new MediaDrawerAdapter(mActivity, false /* useSmallLayout */);
+ // Start with a empty title since we depend on the mMediaManagerListener callback to
+ // know which app is being used and set the actual title there.
+ mRootAdapter.setTitle("");
+
+ // Kick off MediaBrowser/MediaController connection.
+ mMediaPlaybackModel.start();
+ }
+
+ public void cleanup() {
+ mRootAdapter.cleanup();
+ mMediaPlaybackModel.stop();
+ }
+
+ /**
+ * @return Adapter to display root items of MediaBrowse tree. {@link #showQueueInDrawer()} can
+ * be used to display items from the queue.
+ */
+ public CarDrawerAdapter getRootAdapter() {
+ return mRootAdapter;
+ }
+
+ private MediaItemsFetcher createRootMediaItemsFetcher() {
+ return new MediaBrowserItemsFetcher(mActivity, mMediaPlaybackModel,
+ mMediaPlaybackModel.getMediaBrowser().getRoot(), true /* showQueueItem */);
+ }
+
+ private final MediaPlaybackModel.Listener mModelListener =
+ new MediaPlaybackModel.AbstractListener() {
+ @Override
+ public void onMediaAppChanged(@Nullable ComponentName currentName,
+ @Nullable ComponentName newName) {
+ // Only store MediaManager instance to a local variable when it is short lived.
+ MediaManager mediaManager = MediaManager.getInstance(mActivity);
+ mRootAdapter.setTitle(mediaManager.getMediaClientName());
+ }
+
+ @Override
+ public void onMediaConnected() {
+ mRootAdapter.setFetcher(createRootMediaItemsFetcher());
+ }
+ };
+
+ /**
+ * Switch the rootAdapter to show items from the currently playing Queue and open the drawer.
+ * When the drawer is closed, the adapter items are switched back to media-browse root.
+ */
+ public void showQueueInDrawer() {
+ mRootAdapter.setFetcher(new MediaQueueItemsFetcher(mActivity, mMediaPlaybackModel));
+ mRootAdapter.setTitle(mMediaPlaybackModel.getQueueTitle());
+ mActivity.openDrawer();
+ mActivity.addDrawerListener(new DrawerLayout.DrawerListener() {
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ mRootAdapter.setFetcher(createRootMediaItemsFetcher());
+ mActivity.removeDrawerListener(this);
+ mRootAdapter.setTitle(
+ MediaManager.getInstance(mActivity).getMediaClientName());
+ }
+
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {}
+ @Override
+ public void onDrawerOpened(View drawerView) {}
+ @Override
+ public void onDrawerStateChanged(int newState) {}
+ });
+ }
+} \ No newline at end of file
diff --git a/src/com/android/car/media/drawer/MediaItemsFetcher.java b/src/com/android/car/media/drawer/MediaItemsFetcher.java
new file mode 100644
index 00000000..a712f7b7
--- /dev/null
+++ b/src/com/android/car/media/drawer/MediaItemsFetcher.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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.drawer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.MediaDescription;
+
+import com.android.car.app.DrawerItemViewHolder;
+import com.android.car.apps.common.BitmapDownloader;
+import com.android.car.apps.common.BitmapWorkerOptions;
+import com.android.car.apps.common.UriUtils;
+import com.android.car.media.R;
+
+/**
+ * Component that handles fetching of items for {@link MediaDrawerAdapter}.
+ * <p>
+ * It also handles ViewHolder population and item clicks.
+ */
+interface MediaItemsFetcher {
+ /**
+ * Used to inform owning {@link MediaDrawerAdapter} that items have changed.
+ */
+ interface ItemsUpdatedCallback {
+ void onItemsUpdated();
+ }
+
+ /**
+ * Kick-off fetching/monitoring of items.
+ *
+ * @param callback Callback that is invoked when items are first loaded ar if they change
+ * subsequently.
+ */
+ void start(ItemsUpdatedCallback callback);
+
+ /**
+ * @return Number of items currently fetched.
+ */
+ int getItemCount();
+
+ /**
+ * Used by owning {@link MediaDrawerAdapter} to populate views.
+ *
+ * @param holder View-holder to populate.
+ * @param position Item position.
+ */
+ void populateViewHolder(DrawerItemViewHolder holder, int position);
+
+ /**
+ * Used by owning {@link MediaDrawerAdapter} to handle clicks.
+ *
+ * @param position Item position.
+ */
+ void onItemClick(int position);
+
+ /**
+ * Used when this instance is going to be released. Subclasses should release resources.
+ */
+ void cleanup();
+
+ /**
+ * Utility method to populate {@code holder} with details from {@code description}. It populates
+ * title, text and icon at most.
+ */
+ static void populateViewHolderFrom(DrawerItemViewHolder holder, MediaDescription description) {
+ Context context = holder.itemView.getContext();
+ // TODO(sriniv): Once we use smallLayout, text and rightIcon fields may be unavailable.
+ // Related to b/36573125.
+ holder.getTitle().setText(description.getTitle());
+ holder.getText().setText(description.getSubtitle());
+ Bitmap iconBitmap = description.getIconBitmap();
+ holder.getIcon().setImageBitmap(iconBitmap); // Ok to set null here for clearing.
+ if (iconBitmap == null && description.getIconUri() != null) {
+ int bitmapSize =
+ context.getResources().getDimensionPixelSize(R.dimen.car_list_item_icon_size);
+ // We don't want to cache android resources as they are needed to be refreshed after
+ // configuration changes.
+ int cacheFlag = UriUtils.isAndroidResourceUri(description.getIconUri())
+ ? (BitmapWorkerOptions.CACHE_FLAG_DISK_DISABLED
+ | BitmapWorkerOptions.CACHE_FLAG_MEM_DISABLED)
+ : 0;
+ BitmapWorkerOptions options = new BitmapWorkerOptions.Builder(context)
+ .resource(description.getIconUri())
+ .height(bitmapSize)
+ .width(bitmapSize)
+ .cacheFlag(cacheFlag)
+ .build();
+ BitmapDownloader.getInstance(context).loadBitmap(options, holder.getIcon());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/car/media/drawer/MediaQueueItemsFetcher.java b/src/com/android/car/media/drawer/MediaQueueItemsFetcher.java
new file mode 100644
index 00000000..d27d092f
--- /dev/null
+++ b/src/com/android/car/media/drawer/MediaQueueItemsFetcher.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 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.drawer;
+
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+
+import com.android.car.app.CarDrawerActivity;
+import com.android.car.app.DrawerItemViewHolder;
+import com.android.car.media.MediaPlaybackModel;
+import com.android.car.media.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link MediaItemsFetcher} implementation that fetches items from the {@link MediaController}'s
+ * currently playing queue.
+ */
+class MediaQueueItemsFetcher implements MediaItemsFetcher {
+ private static final String TAG = "MediaQueueItemsFetcher";
+
+ private final Handler mHandler = new Handler();
+ private final CarDrawerActivity mActivity;
+ private MediaPlaybackModel mMediaPlaybackModel;
+ private ItemsUpdatedCallback mCallback;
+ private List<MediaSession.QueueItem> mItems = new ArrayList<>();
+
+ MediaQueueItemsFetcher(CarDrawerActivity activity, MediaPlaybackModel model) {
+ mActivity = activity;
+ mMediaPlaybackModel = model;
+ }
+
+ @Override
+ public void start(ItemsUpdatedCallback callback) {
+ mCallback = callback;
+ if (mMediaPlaybackModel != null) {
+ mMediaPlaybackModel.addListener(listener);
+ updateItemsFrom(mMediaPlaybackModel.getQueue());
+ }
+ // Inform client of current items. Invoke async to avoid re-entrancy issues.
+ mHandler.post(mCallback::onItemsUpdated);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public void populateViewHolder(DrawerItemViewHolder holder, int position) {
+ MediaSession.QueueItem item = mItems.get(position);
+ MediaItemsFetcher.populateViewHolderFrom(holder, item.getDescription());
+
+ if (item.getQueueId() == getActiveQueueItemId()) {
+ int primaryColor = mMediaPlaybackModel.getPrimaryColor();
+ Drawable drawable =
+ mActivity.getResources().getDrawable(R.drawable.ic_music_active);
+ drawable.setColorFilter(primaryColor, PorterDuff.Mode.SRC_IN);
+ holder.getRightIcon().setImageDrawable(drawable);
+ } else {
+ holder.getRightIcon().setImageBitmap(null);
+ }
+ }
+
+ @Override
+ public void onItemClick(int position) {
+ MediaController.TransportControls controls = mMediaPlaybackModel.getTransportControls();
+ if (controls != null) {
+ controls.skipToQueueItem(mItems.get(position).getQueueId());
+ }
+ mActivity.closeDrawer();
+ }
+
+ @Override
+ public void cleanup() {
+ mMediaPlaybackModel.removeListener(listener);
+ }
+
+ private void updateItemsFrom(List<MediaSession.QueueItem> queue) {
+ mItems.clear();
+ // We only show items starting from active-item in the queue.
+ final long activeItemId = getActiveQueueItemId();
+ boolean activeItemFound = false;
+ for (MediaSession.QueueItem item : queue) {
+ if (activeItemId == item.getQueueId()) {
+ activeItemFound = true;
+ }
+ if (activeItemFound) {
+ mItems.add(item);
+ }
+ }
+ }
+
+ private long getActiveQueueItemId() {
+ if (mMediaPlaybackModel != null) {
+ PlaybackState playbackState = mMediaPlaybackModel.getPlaybackState();
+ if (playbackState != null) {
+ return playbackState.getActiveQueueItemId();
+ }
+ }
+ return MediaSession.QueueItem.UNKNOWN_ID;
+ }
+
+ private final MediaPlaybackModel.Listener listener = new MediaPlaybackModel.AbstractListener() {
+ @Override
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+ updateItemsFrom(queue);
+ mCallback.onItemsUpdated();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+ // Since active playing item may have changed, force re-draw of queue items.
+ mCallback.onItemsUpdated();
+ }
+
+ @Override
+ public void onSessionDestroyed(CharSequence destroyedMediaClientName) {
+ onQueueChanged(Collections.emptyList());
+ }
+ };
+}