1 /*
2  * Copyright (C) 2015 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 
17 package com.android.tv.menu;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.media.tv.TvInputInfo;
22 import android.view.View;
23 import android.view.accessibility.AccessibilityManager;
24 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
25 import com.android.tv.ChannelChanger;
26 import com.android.tv.R;
27 import com.android.tv.TvSingletons;
28 import com.android.tv.analytics.Tracker;
29 import com.android.tv.common.feature.CommonFeatures;
30 import com.android.tv.data.ChannelImpl;
31 import com.android.tv.data.api.Channel;
32 import com.android.tv.dvr.DvrDataManager;
33 import com.android.tv.recommendation.Recommender;
34 import com.android.tv.util.TvInputManagerHelper;
35 import java.util.ArrayDeque;
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /** An adapter of the Channels row. */
40 public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<ChannelsRowItem>
41         implements AccessibilityStateChangeListener {
42 
43     private final Context mContext;
44     private final Tracker mTracker;
45     private final Recommender mRecommender;
46     private final DvrDataManager mDvrDataManager;
47     private final int mMaxCount;
48     private final int mMinCount;
49     private final ChannelChanger mChannelChanger;
50     private final AccessibilityManager mAccessibilityManager;
51 
52     private boolean mShowChannelUpDown;
53 
ChannelsRowAdapter( Context context, Recommender recommender, int minCount, int maxCount)54     public ChannelsRowAdapter(
55             Context context, Recommender recommender, int minCount, int maxCount) {
56         super(context);
57         mContext = context;
58         TvSingletons tvSingletons = TvSingletons.getSingletons(context);
59         mTracker = tvSingletons.getTracker();
60         if (CommonFeatures.DVR.isEnabled(context)) {
61             mDvrDataManager = tvSingletons.getDvrDataManager();
62         } else {
63             mDvrDataManager = null;
64         }
65         mRecommender = recommender;
66         mMinCount = minCount;
67         mMaxCount = maxCount;
68         setHasStableIds(true);
69         mChannelChanger = (ChannelChanger) (context);
70         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
71         mShowChannelUpDown = mAccessibilityManager.isEnabled();
72         mAccessibilityManager.addAccessibilityStateChangeListener(this);
73     }
74 
75     @Override
getItemViewType(int position)76     public int getItemViewType(int position) {
77         return getItemList().get(position).getLayoutId();
78     }
79 
80     @Override
getLayoutResId(int viewType)81     protected int getLayoutResId(int viewType) {
82         return viewType;
83     }
84 
85     @Override
getItemId(int position)86     public long getItemId(int position) {
87         return getItemList().get(position).getItemId();
88     }
89 
90     @Override
onBindViewHolder(MyViewHolder viewHolder, int position)91     public void onBindViewHolder(MyViewHolder viewHolder, int position) {
92         int viewType = getItemViewType(position);
93         if (viewType == R.layout.menu_card_guide) {
94             viewHolder.itemView.setOnClickListener(this::onGuideClicked);
95         } else if (viewType == R.layout.menu_card_up) {
96             viewHolder.itemView.setOnClickListener(this::onChannelUpClicked);
97         } else if (viewType == R.layout.menu_card_down) {
98             viewHolder.itemView.setOnClickListener(this::onChannelDownClicked);
99         } else if (viewType == R.layout.menu_card_setup) {
100             viewHolder.itemView.setOnClickListener(this::onSetupClicked);
101         } else if (viewType == R.layout.menu_card_app_link) {
102             viewHolder.itemView.setOnClickListener(this::onAppLinkClicked);
103         } else if (viewType == R.layout.menu_card_dvr) {
104             viewHolder.itemView.setOnClickListener(this::onDvrClicked);
105             SimpleCardView view = (SimpleCardView) viewHolder.itemView;
106             view.setText(R.string.channels_item_dvr);
107         } else {
108             viewHolder.itemView.setTag(getItemList().get(position).getChannel());
109             viewHolder.itemView.setOnClickListener(this::onChannelClicked);
110         }
111         super.onBindViewHolder(viewHolder, position);
112     }
113 
114     @Override
update()115     public void update() {
116         if (getItemCount() == 0) {
117             createItems();
118         } else {
119             updateItems();
120         }
121     }
122 
onGuideClicked(View unused)123     private void onGuideClicked(View unused) {
124         mTracker.sendMenuClicked(R.string.channels_item_program_guide);
125         getMainActivity().getOverlayManager().showProgramGuide();
126     }
127 
onChannelDownClicked(View unused)128     private void onChannelDownClicked(View unused) {
129         mChannelChanger.channelDown();
130     }
131 
onChannelUpClicked(View unused)132     private void onChannelUpClicked(View unused) {
133         mChannelChanger.channelUp();
134     }
135 
onSetupClicked(View unused)136     private void onSetupClicked(View unused) {
137         mTracker.sendMenuClicked(R.string.channels_item_setup);
138         getMainActivity().getOverlayManager().showSetupFragment();
139     }
140 
onDvrClicked(View unused)141     private void onDvrClicked(View unused) {
142         mTracker.sendMenuClicked(R.string.channels_item_dvr);
143         getMainActivity().getOverlayManager().showDvrManager();
144     }
145 
onAppLinkClicked(View view)146     private void onAppLinkClicked(View view) {
147         mTracker.sendMenuClicked(R.string.channels_item_app_link);
148         Intent intent = ((AppLinkCardView) view).getIntent();
149         if (intent != null) {
150             getMainActivity().startActivitySafe(intent);
151         }
152     }
153 
onChannelClicked(View view)154     private void onChannelClicked(View view) {
155         // Always send the label "Channels" because the channel ID or name or number might be
156         // sensitive.
157         mTracker.sendMenuClicked(R.string.menu_title_channels);
158         getMainActivity().tuneToChannel((Channel) view.getTag());
159         getMainActivity().hideOverlaysForTune();
160     }
161 
createItems()162     private void createItems() {
163         List<ChannelsRowItem> items = new ArrayList<>();
164         items.add(ChannelsRowItem.GUIDE_ITEM);
165         if (mShowChannelUpDown) {
166             items.add(ChannelsRowItem.UP_ITEM);
167             items.add(ChannelsRowItem.DOWN_ITEM);
168         }
169 
170         if (needToShowSetupItem()) {
171             items.add(ChannelsRowItem.SETUP_ITEM);
172         }
173         if (needToShowDvrItem()) {
174             items.add(ChannelsRowItem.DVR_ITEM);
175         }
176         if (needToShowAppLinkItem()) {
177             ChannelsRowItem.APP_LINK_ITEM.setChannel(
178                     new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build());
179             items.add(ChannelsRowItem.APP_LINK_ITEM);
180         }
181         for (Channel channel : getRecentChannels()) {
182             items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel));
183         }
184         setItemList(items);
185     }
186 
updateItems()187     private void updateItems() {
188         List<ChannelsRowItem> items = getItemList();
189         // The current index of the item list to iterate. It starts from 1 because the first item
190         // (GUIDE) is always visible and not updated.
191         int currentIndex = 1;
192         if (updateItem(mShowChannelUpDown, ChannelsRowItem.UP_ITEM, currentIndex)) {
193             ++currentIndex;
194         }
195         if (updateItem(mShowChannelUpDown, ChannelsRowItem.DOWN_ITEM, currentIndex)) {
196             ++currentIndex;
197         }
198         if (updateItem(needToShowSetupItem(), ChannelsRowItem.SETUP_ITEM, currentIndex)) {
199             ++currentIndex;
200         }
201         if (updateItem(needToShowDvrItem(), ChannelsRowItem.DVR_ITEM, currentIndex)) {
202             ++currentIndex;
203         }
204         if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) {
205             if (!getMainActivity()
206                     .getCurrentChannel()
207                     .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) {
208                 ChannelsRowItem.APP_LINK_ITEM.setChannel(
209                         new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build());
210                 notifyItemChanged(currentIndex);
211             }
212             ++currentIndex;
213         }
214         int numOldChannels = items.size() - currentIndex;
215         if (numOldChannels > 0) {
216             while (items.size() > currentIndex) {
217                 items.remove(items.size() - 1);
218             }
219             notifyItemRangeRemoved(currentIndex, numOldChannels);
220         }
221         for (Channel channel : getRecentChannels()) {
222             items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel));
223         }
224         int numNewChannels = items.size() - currentIndex;
225         if (numNewChannels > 0) {
226             notifyItemRangeInserted(currentIndex, numNewChannels);
227         }
228     }
229 
230     /** Returns {@code true} if the item should be shown. */
updateItem(boolean needToShow, ChannelsRowItem item, int index)231     private boolean updateItem(boolean needToShow, ChannelsRowItem item, int index) {
232         List<ChannelsRowItem> items = getItemList();
233         boolean isItemInList = index < items.size() && item.equals(items.get(index));
234         if (needToShow && !isItemInList) {
235             items.add(index, item);
236             notifyItemInserted(index);
237         } else if (!needToShow && isItemInList) {
238             items.remove(index);
239             notifyItemRemoved(index);
240         }
241         return needToShow;
242     }
243 
244     private boolean needToShowSetupItem() {
245         TvSingletons singletons = TvSingletons.getSingletons(mContext);
246         TvInputManagerHelper inputManager = singletons.getTvInputManagerHelper();
247         return singletons.getSetupUtils().hasNewInput(inputManager);
248     }
249 
250     private boolean needToShowDvrItem() {
251         TvInputManagerHelper inputManager =
252                 TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
253         if (mDvrDataManager != null) {
254             for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) {
255                 if (info.canRecord()) {
256                     return true;
257                 }
258             }
259         }
260         return false;
261     }
262 
263     private boolean needToShowAppLinkItem() {
264         TvInputManagerHelper inputManager =
265                 TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
266         Channel currentChannel = getMainActivity().getCurrentChannel();
267         return currentChannel != null
268                 && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE
269                 // Sometimes applicationInfo can be null. b/28932537
270                 && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null;
271     }
272 
273     private List<Channel> getRecentChannels() {
274         List<Channel> channelList = new ArrayList<>();
275         long currentChannelId = getMainActivity().getCurrentChannelId();
276         ArrayDeque<Long> recentChannels = getMainActivity().getRecentChannels();
277         // Add the last watched channel as the first one.
278         for (long channelId : recentChannels) {
279             if (addChannelToList(
280                     channelList, mRecommender.getChannel(channelId), currentChannelId)) {
281                 break;
282             }
283         }
284         // Add the recommended channels.
285         for (Channel channel : mRecommender.recommendChannels(mMaxCount)) {
286             if (channelList.size() >= mMaxCount) {
287                 break;
288             }
289             addChannelToList(channelList, channel, currentChannelId);
290         }
291         // If the number of recommended channels is not enough, add more from the recent channel
292         // list.
293         for (long channelId : recentChannels) {
294             if (channelList.size() >= mMinCount) {
295                 break;
296             }
addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId)297             addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId);
298         }
299         return channelList;
300     }
301 
addChannelToList( List<Channel> channelList, Channel channel, long currentChannelId)302     private static boolean addChannelToList(
303             List<Channel> channelList, Channel channel, long currentChannelId) {
304         if (channel == null
305                 || channel.getId() == currentChannelId
306                 || channelList.contains(channel)
307                 || !channel.isBrowsable()) {
308             return false;
309         }
310         channelList.add(channel);
311         return true;
312     }
313 
314     @Override
onAccessibilityStateChanged(boolean enabled)315     public void onAccessibilityStateChanged(boolean enabled) {
316         mShowChannelUpDown = enabled;
317         update();
318     }
319 
320     @Override
release()321     public void release() {
322         mAccessibilityManager.removeAccessibilityStateChangeListener(this);
323         super.release();
324     }
325 }
326