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