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 */ 16package com.android.car.media.drawer; 17 18import android.content.Context; 19import android.graphics.PorterDuff; 20import android.graphics.drawable.Drawable; 21import android.media.session.MediaController; 22import android.media.session.MediaSession; 23import android.media.session.PlaybackState; 24import android.os.Handler; 25import android.support.annotation.Nullable; 26 27import com.android.car.media.MediaPlaybackModel; 28import com.android.car.media.R; 29 30import java.util.ArrayList; 31import java.util.Collections; 32import java.util.List; 33 34import androidx.car.drawer.DrawerItemViewHolder; 35 36/** 37 * {@link MediaItemsFetcher} implementation that fetches items from the {@link MediaController}'s 38 * currently playing queue. 39 */ 40class MediaQueueItemsFetcher implements MediaItemsFetcher { 41 private final Handler mHandler = new Handler(); 42 private final Context mContext; 43 private final MediaItemOnClickListener mClickListener; 44 private MediaPlaybackModel mMediaPlaybackModel; 45 private ItemsUpdatedCallback mCallback; 46 private List<MediaSession.QueueItem> mItems = new ArrayList<>(); 47 48 MediaQueueItemsFetcher(Context context, MediaPlaybackModel model, 49 MediaItemOnClickListener listener) { 50 mContext = context; 51 mMediaPlaybackModel = model; 52 mClickListener = listener; 53 } 54 55 @Override 56 public void start(ItemsUpdatedCallback callback) { 57 mCallback = callback; 58 if (mMediaPlaybackModel != null) { 59 mMediaPlaybackModel.addListener(mListener); 60 updateItemsFrom(mMediaPlaybackModel.getQueue()); 61 } 62 // Inform client of current items. Invoke async to avoid re-entrancy issues. 63 mHandler.post(mCallback::onItemsUpdated); 64 } 65 66 @Override 67 public int getItemCount() { 68 return mItems.size(); 69 } 70 71 @Override 72 public boolean usesSmallLayout(int position) { 73 return MediaItemsFetcher.usesSmallLayout(mItems.get(position).getDescription()); 74 } 75 76 @Override 77 public void populateViewHolder(DrawerItemViewHolder holder, int position) { 78 MediaSession.QueueItem item = mItems.get(position); 79 MediaItemsFetcher.populateViewHolderFrom(holder, item.getDescription()); 80 81 if (holder.getEndIcon() == null) { 82 return; 83 } 84 85 if (item.getQueueId() == getActiveQueueItemId()) { 86 int primaryColor = mMediaPlaybackModel.getPrimaryColor(); 87 Drawable drawable = 88 mContext.getDrawable(R.drawable.ic_music_active); 89 drawable.setColorFilter(primaryColor, PorterDuff.Mode.SRC_IN); 90 holder.getEndIcon().setImageDrawable(drawable); 91 } else { 92 holder.getEndIcon().setImageBitmap(null); 93 } 94 } 95 96 @Override 97 public void onItemClick(int position) { 98 if (mClickListener != null) { 99 mClickListener.onQueueItemClicked(mItems.get(position)); 100 } 101 } 102 103 @Override 104 public void cleanup() { 105 mMediaPlaybackModel.removeListener(mListener); 106 } 107 108 @Override 109 public int getScrollPosition() { 110 long activeId = getActiveQueueItemId(); 111 // A linear scan isn't really the best thing to do for large lists but we suspect that 112 // the queue isn't going to be very long anyway so we can just do the trivial thing. If 113 // it starts becoming a problem, we can build an index over the ids. 114 for (int position = 0; position < mItems.size(); position++) { 115 MediaSession.QueueItem item = mItems.get(position); 116 if (item.getQueueId() == activeId) { 117 return position; 118 } 119 } 120 return MediaItemsFetcher.DONT_SCROLL; 121 } 122 123 private void updateItemsFrom(List<MediaSession.QueueItem> queue) { 124 mItems.clear(); 125 mItems.addAll(queue); 126 } 127 128 private long getActiveQueueItemId() { 129 if (mMediaPlaybackModel != null) { 130 PlaybackState playbackState = mMediaPlaybackModel.getPlaybackState(); 131 if (playbackState != null) { 132 return playbackState.getActiveQueueItemId(); 133 } 134 } 135 return MediaSession.QueueItem.UNKNOWN_ID; 136 } 137 138 private final MediaPlaybackModel.Listener mListener = 139 new MediaPlaybackModel.AbstractListener() { 140 @Override 141 public void onQueueChanged(List<MediaSession.QueueItem> queue) { 142 updateItemsFrom(queue); 143 mCallback.onItemsUpdated(); 144 } 145 146 @Override 147 public void onPlaybackStateChanged(@Nullable PlaybackState state) { 148 // Since active playing item may have changed, force re-draw of queue items. 149 mCallback.onItemsUpdated(); 150 } 151 152 @Override 153 public void onSessionDestroyed(CharSequence destroyedMediaClientName) { 154 onQueueChanged(Collections.emptyList()); 155 } 156 }; 157} 158