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