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.MediaDescription;
22import android.media.browse.MediaBrowser;
23import android.media.session.MediaSession;
24import android.util.Log;
25
26import com.android.car.media.MediaPlaybackModel;
27import com.android.car.media.R;
28
29import java.util.ArrayList;
30import java.util.List;
31
32import 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 */
40class 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