• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016, 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.overview;
17 
18 import android.content.Context;
19 import android.support.annotation.Nullable;
20 import android.support.car.ui.PagedListView;
21 import android.support.v7.widget.CardView;
22 import android.support.v7.widget.RecyclerView;
23 import android.util.Log;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import com.android.car.stream.AbstractBundleable;
28 import com.android.car.stream.MediaPlaybackExtension;
29 import com.android.car.stream.StreamCard;
30 import com.android.car.stream.StreamConstants;
31 
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 
35 /**
36  * A {@link RecyclerView.Adapter} that binds {@link StreamCard} to their respective views.
37  */
38 public class StreamAdapter extends RecyclerView.Adapter<StreamViewHolder>
39         implements PagedListView.ItemCap {
40 
41     private static final int BASIC_CARD_LAYOUT_TYPE = 0;
42     private static final int CURRENT_CALL_CARD_LAYOUT_TYPE = 1;
43     private static final int MEDIA_CARD_LAYOUT_TYPE = 2;
44 
45     private static final String TAG = "StreamAdapter";
46 
47     private static final int MAX_NUMBER_ITEMS = 25;
48     private int mMaxItems = MAX_NUMBER_ITEMS;
49 
50     private final ArrayList<StreamCard> mStreamCards = new ArrayList<>(mMaxItems);
51     private final Context mContext;
52 
StreamAdapter(Context context)53     public StreamAdapter(Context context) {
54         mContext = context;
55     }
56 
57     @Override
onCreateViewHolder(ViewGroup parent, int viewType)58     public StreamViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
59         View view = LayoutInflater.from(mContext)
60                 .inflate(R.layout.stream_card_container, parent, false);
61         CardView container = (CardView) view.findViewById(R.id.stream_item_container);
62         switch (viewType) {
63             case CURRENT_CALL_CARD_LAYOUT_TYPE:
64                 container.addView(LayoutInflater.from(parent.getContext())
65                         .inflate(R.layout.stream_card_current_call, container, false));
66                 return new CurrentCallStreamViewHolder(mContext, view);
67             case MEDIA_CARD_LAYOUT_TYPE:
68                 container.addView(LayoutInflater.from(parent.getContext())
69                         .inflate(R.layout.stream_card_media, container, false));
70                 return new MediaStreamViewHolder(mContext, view);
71             default:
72                 // For all cards that do not have their own view holder/layout, use the basic stream
73                 // card layout.
74                 View contentView = LayoutInflater.from(parent.getContext())
75                         .inflate(R.layout.stream_card_simple, container, false);
76                 container.addView(contentView);
77 
78                 return new SimpleStreamViewHolder(mContext, view);
79         }
80     }
81 
82     @Override
onBindViewHolder(StreamViewHolder holder, int position)83     public void onBindViewHolder(StreamViewHolder holder, int position) {
84         if (Log.isLoggable(TAG, Log.DEBUG)) {
85             Log.d(TAG, "Stream Card being bound: " + mStreamCards.get(position));
86         }
87         holder.bindStreamCard(mStreamCards.get(position));
88     }
89 
90     @Override
getItemViewType(int position)91     public int getItemViewType(int position) {
92         StreamCard card = mStreamCards.get(position);
93 
94         // If the card has no extensions, then render as a basic card.
95         if (card.getCardExtension() == null) {
96             return BASIC_CARD_LAYOUT_TYPE;
97         }
98 
99         switch (card.getType()) {
100             case StreamConstants.CARD_TYPE_CURRENT_CALL:
101                 return CURRENT_CALL_CARD_LAYOUT_TYPE;
102             case StreamConstants.CARD_TYPE_MEDIA:
103                 return MEDIA_CARD_LAYOUT_TYPE;
104             default:
105                 return BASIC_CARD_LAYOUT_TYPE;
106         }
107     }
108 
109     @Override
getItemCount()110     public int getItemCount() {
111         return mStreamCards.size();
112     }
113 
114     @Override
setMaxItems(int max)115     public void setMaxItems(int max) {
116         if (max < 1) {
117             return;
118         }
119         mMaxItems = Math.min(max, MAX_NUMBER_ITEMS);
120     }
121 
122     /**
123      * Remove all {@link StreamCard} in the adapter.
124      */
removeAllCards()125     public void removeAllCards() {
126         mStreamCards.clear();
127         notifyDataSetChanged();
128     }
129 
addCard(StreamCard card)130     public void addCard(StreamCard card) {
131         // There should only be one card in the stream that is of type MEDIA. As a result, handle
132         // this case specially. Otherwise, check if the card matches a stream card that already
133         // exists and replace it.
134         if (card.getType() == StreamConstants.CARD_TYPE_MEDIA && !canAddMediaCard(card)) {
135             if (Log.isLoggable(TAG, Log.DEBUG)) {
136                 Log.d(TAG, "Card: " + card + " does not have focus, so will not be added.");
137             }
138             return;
139         } else if (maybeReplaceCard(card)) {
140             if (Log.isLoggable(TAG, Log.DEBUG)) {
141                 Log.d(TAG, "Card: " + card + " was replaced in stream");
142             }
143             return;
144         }
145 
146         if (mStreamCards.size() >= mMaxItems) {
147             StreamCard removedCard = mStreamCards.remove(mStreamCards.size() - 1);
148             if (Log.isLoggable(TAG, Log.DEBUG)) {
149                 Log.d(TAG, "Card: " + removedCard + " was pushed out the stream");
150             }
151         }
152 
153         int size = mStreamCards.size();
154         for (int i = 0; i < size; i++) {
155             if (mStreamCards.get(i).getPriority() <= card.getPriority()) {
156                 if (Log.isLoggable(TAG, Log.DEBUG)) {
157                     Log.d(TAG, "Card: " + card + " was inserted at i: " + i);
158                 }
159                 mStreamCards.add(i, card);
160                 notifyDataSetChanged();
161                 return;
162             }
163         }
164 
165         // The card had lower priority than all existing cards, add to the end.
166         if (Log.isLoggable(TAG, Log.DEBUG)) {
167             Log.d(TAG, "Card: " + card + " was inserted at the end");
168         }
169         mStreamCards.add(card);
170         notifyDataSetChanged();
171     }
172 
removeCard(StreamCard card)173     public void removeCard(StreamCard card) {
174         for (Iterator<StreamCard> iterator = mStreamCards.iterator(); iterator.hasNext();) {
175             StreamCard existingCard = iterator.next();
176             if (existingCard.getType() == card.getType() && existingCard.getId() == card.getId()) {
177                 iterator.remove();
178                 notifyDataSetChanged();
179                 break;
180             }
181         }
182     }
183 
184     /**
185      * Replaces a card in the adapter if the new card has the same priority. Otherwise it removes
186      * the card from the adapter.
187      */
maybeReplaceCard(StreamCard newCard)188     private boolean maybeReplaceCard(StreamCard newCard) {
189         for (int i = 0, size = mStreamCards.size(); i < size; i++) {
190             StreamCard existingCard = mStreamCards.get(i);
191 
192             if (existingCard.getType() == newCard.getType()
193                     && existingCard.getId() == newCard.getId()) {
194                 mStreamCards.set(i, newCard);
195                 if (existingCard.getPriority() == newCard.getPriority()) {
196                     mStreamCards.set(i, newCard);
197                     notifyDataSetChanged();
198                     return true;
199                 } else {
200                     // If the priority is no longer the same, just remove the card
201                     // and let it be added again.
202                     mStreamCards.remove(i);
203                     return false;
204                 }
205             }
206         }
207         return false;
208     }
209 
210     /**
211      * Searches through {@link #mStreamCards} and returns the first card in the list that has a
212      * card type of {@link StreamConstants#CARD_TYPE_MEDIA}. If none is found, then {@code null}
213      * is returned.
214      */
215     @Nullable
getExistingMediaCard()216     private StreamCard getExistingMediaCard() {
217         for (StreamCard streamCard : mStreamCards) {
218             if (streamCard.getType() == StreamConstants.CARD_TYPE_MEDIA) {
219                 return streamCard;
220             }
221         }
222 
223         return null;
224     }
225 
226     /**
227      * Returns {@code true} if the given {@link StreamCard} of type
228      * {@link StreamConstants#CARD_TYPE_MEDIA} can be added to the set of stream cards. This method
229      * is responsible for ensuring that there is only a single instance of a media card at all
230      * times.
231      */
canAddMediaCard(StreamCard card)232     private boolean canAddMediaCard(StreamCard card) {
233         StreamCard existingMediaCard = getExistingMediaCard();
234 
235         // If there is no other media StreamCard, then it is ok to add.
236         if (existingMediaCard == null) {
237             return true;
238         }
239 
240         // If this update is coming from the same application, then add the StreamCard.
241         if (existingMediaCard.getId() == card.getId()) {
242             return true;
243         }
244 
245         AbstractBundleable cardExtension = card.getCardExtension();
246 
247         // Cannot infer play state from the card to be added, so just add it.
248         if (!(cardExtension instanceof MediaPlaybackExtension)) {
249             return true;
250         }
251 
252         // Otherwise, ensure only the application that currently has focus has the ability to show
253         // their card. When a card is currently playing, it implies that it has focus.
254         boolean hasFocus = ((MediaPlaybackExtension) cardExtension).isPlaying();
255 
256         // Since this new card has focus, remove the existing card from the list.
257         if (hasFocus) {
258             mStreamCards.remove(existingMediaCard);
259         }
260 
261         return hasFocus;
262     }
263 }
264