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