1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.media.drawer; 17 18 import android.content.Context; 19 import android.graphics.PorterDuff; 20 import android.graphics.drawable.Drawable; 21 import android.media.MediaDescription; 22 import android.media.browse.MediaBrowser; 23 import android.media.session.MediaSession; 24 import android.util.Log; 25 26 import com.android.car.media.MediaPlaybackModel; 27 import com.android.car.media.R; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 import androidx.car.drawer.DrawerItemViewHolder; 33 34 /** 35 * {@link MediaItemsFetcher} implementation that fetches items from a specific {@link MediaBrowser} 36 * node. 37 * <p> 38 * It optionally supports surfacing the Media app's queue as the last item. 39 */ 40 class MediaBrowserItemsFetcher implements MediaItemsFetcher { 41 private static final String TAG = "Media.BrowserFetcher"; 42 43 /** 44 * An id that can be returned from {@link MediaBrowser.MediaItem#getMediaId()} to indicate that 45 * a {@link android.media.browse.MediaBrowser.MediaItem} representing the play queue has been 46 * clicked. 47 */ 48 static final String PLAY_QUEUE_MEDIA_ID = "com.android.car.media.drawer.PLAY_QUEUE"; 49 50 private final Context mContext; 51 private final MediaPlaybackModel mMediaPlaybackModel; 52 private final String mMediaId; 53 private final boolean mShowQueueItem; 54 private final MediaItemOnClickListener mItemClickListener; 55 private ItemsUpdatedCallback mCallback; 56 private List<MediaBrowser.MediaItem> mItems = new ArrayList<>(); 57 private boolean mQueueAvailable; 58 59 MediaBrowserItemsFetcher(Context context, MediaPlaybackModel model, 60 MediaItemOnClickListener listener, String mediaId, boolean showQueueItem) { 61 mContext = context; 62 mMediaPlaybackModel = model; 63 mItemClickListener = listener; 64 mMediaId = mediaId; 65 mShowQueueItem = showQueueItem; 66 } 67 68 @Override 69 public void start(ItemsUpdatedCallback callback) { 70 mCallback = callback; 71 updateQueueAvailability(); 72 if (mMediaPlaybackModel.isConnected()) { 73 mMediaPlaybackModel.getMediaBrowser().subscribe(mMediaId, mSubscriptionCallback); 74 } else { 75 mItems.clear(); 76 callback.onItemsUpdated(); 77 } 78 mMediaPlaybackModel.addListener(mModelListener); 79 } 80 81 private final MediaPlaybackModel.Listener mModelListener = 82 new MediaPlaybackModel.AbstractListener() { 83 @Override 84 public void onQueueChanged(List<MediaSession.QueueItem> queue) { 85 updateQueueAvailability(); 86 } 87 @Override 88 public void onSessionDestroyed(CharSequence destroyedMediaClientName) { 89 updateQueueAvailability(); 90 } 91 @Override 92 public void onMediaConnectionSuspended() { 93 if (mCallback != null) { 94 mCallback.onItemsUpdated(); 95 } 96 } 97 }; 98 99 private final MediaBrowser.SubscriptionCallback mSubscriptionCallback = 100 new MediaBrowser.SubscriptionCallback() { 101 @Override 102 public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { 103 mItems.clear(); 104 mItems.addAll(children); 105 mCallback.onItemsUpdated(); 106 } 107 108 @Override 109 public void onError(String parentId) { 110 mItems.clear(); 111 mCallback.onItemsUpdated(); 112 } 113 }; 114 115 private void updateQueueAvailability() { 116 if (mShowQueueItem && !mMediaPlaybackModel.getQueue().isEmpty()) { 117 mQueueAvailable = true; 118 } 119 } 120 121 @Override 122 public int getItemCount() { 123 int size = mItems.size(); 124 if (mQueueAvailable) { 125 size++; 126 } 127 return size; 128 } 129 130 @Override 131 public boolean usesSmallLayout(int position) { 132 if (mQueueAvailable && position == mItems.size()) { 133 return true; 134 } 135 return MediaItemsFetcher.usesSmallLayout(mItems.get(position).getDescription()); 136 } 137 138 @Override 139 public void populateViewHolder(DrawerItemViewHolder holder, int position) { 140 if (mQueueAvailable && position == mItems.size()) { 141 holder.getTitle().setText(mMediaPlaybackModel.getQueueTitle()); 142 return; 143 } 144 MediaBrowser.MediaItem item = mItems.get(position); 145 MediaItemsFetcher.populateViewHolderFrom(holder, item.getDescription()); 146 147 if (holder.getEndIcon() == null) { 148 return; 149 } 150 151 if (item.isBrowsable()) { 152 int iconColor = mContext.getColor(R.color.car_tint); 153 Drawable drawable = mContext.getDrawable(R.drawable.ic_chevron_right); 154 drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); 155 holder.getEndIcon().setImageDrawable(drawable); 156 } else { 157 holder.getEndIcon().setImageDrawable(null); 158 } 159 } 160 161 @Override 162 public void onItemClick(int position) { 163 if (mItemClickListener == null) { 164 return; 165 } 166 167 MediaBrowser.MediaItem item = mQueueAvailable && position == mItems.size() 168 ? createPlayQueueMediaItem() 169 : mItems.get(position); 170 171 mItemClickListener.onMediaItemClicked(item); 172 } 173 174 /** 175 * Creates and returns a {@link android.media.browse.MediaBrowser.MediaItem} that represents an 176 * entry for the play queue. A play queue media item will have a media id of 177 * {@link #PLAY_QUEUE_MEDIA_ID} and is {@link MediaBrowser.MediaItem#FLAG_BROWSABLE}. 178 */ 179 private MediaBrowser.MediaItem createPlayQueueMediaItem() { 180 MediaDescription description = new MediaDescription.Builder() 181 .setMediaId(PLAY_QUEUE_MEDIA_ID) 182 .setTitle(mMediaPlaybackModel.getQueueTitle()) 183 .build(); 184 185 return new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE); 186 } 187 188 @Override 189 public void cleanup() { 190 mMediaPlaybackModel.removeListener(mModelListener); 191 mMediaPlaybackModel.getMediaBrowser().unsubscribe(mMediaId); 192 mCallback = null; 193 } 194 195 @Override 196 public int getScrollPosition() { 197 return MediaItemsFetcher.DONT_SCROLL; 198 } 199 } 200