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 com.android.tv.R; 24 import com.android.tv.TvSingletons; 25 import com.android.tv.analytics.Tracker; 26 import com.android.tv.common.feature.CommonFeatures; 27 import com.android.tv.data.ChannelImpl; 28 import com.android.tv.data.api.Channel; 29 import com.android.tv.dvr.DvrDataManager; 30 import com.android.tv.recommendation.Recommender; 31 import com.android.tv.util.TvInputManagerHelper; 32 import java.util.ArrayDeque; 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** An adapter of the Channels row. */ 37 public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<ChannelsRowItem> { 38 // There are four special cards: guide, setup, dvr, applink. 39 private static final int SIZE_OF_VIEW_TYPE = 5; 40 41 private final Context mContext; 42 private final Tracker mTracker; 43 private final Recommender mRecommender; 44 private final DvrDataManager mDvrDataManager; 45 private final int mMaxCount; 46 private final int mMinCount; 47 48 private final View.OnClickListener mGuideOnClickListener = 49 new View.OnClickListener() { 50 @Override 51 public void onClick(View view) { 52 mTracker.sendMenuClicked(R.string.channels_item_program_guide); 53 getMainActivity().getOverlayManager().showProgramGuide(); 54 } 55 }; 56 57 private final View.OnClickListener mSetupOnClickListener = 58 new View.OnClickListener() { 59 @Override 60 public void onClick(View view) { 61 mTracker.sendMenuClicked(R.string.channels_item_setup); 62 getMainActivity().getOverlayManager().showSetupFragment(); 63 } 64 }; 65 66 private final View.OnClickListener mDvrOnClickListener = 67 new View.OnClickListener() { 68 @Override 69 public void onClick(View view) { 70 mTracker.sendMenuClicked(R.string.channels_item_dvr); 71 getMainActivity().getOverlayManager().showDvrManager(); 72 } 73 }; 74 75 private final View.OnClickListener mAppLinkOnClickListener = 76 new View.OnClickListener() { 77 @Override 78 public void onClick(View view) { 79 mTracker.sendMenuClicked(R.string.channels_item_app_link); 80 Intent intent = ((AppLinkCardView) view).getIntent(); 81 if (intent != null) { 82 getMainActivity().startActivitySafe(intent); 83 } 84 } 85 }; 86 87 private final View.OnClickListener mChannelOnClickListener = 88 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 92 // might be 93 // sensitive. 94 mTracker.sendMenuClicked(R.string.menu_title_channels); 95 getMainActivity().tuneToChannel((Channel) view.getTag()); 96 getMainActivity().hideOverlaysForTune(); 97 } 98 }; 99 100 public ChannelsRowAdapter( 101 Context context, Recommender recommender, int minCount, int maxCount) { 102 super(context); 103 mContext = context; 104 TvSingletons tvSingletons = TvSingletons.getSingletons(context); 105 mTracker = tvSingletons.getTracker(); 106 if (CommonFeatures.DVR.isEnabled(context)) { 107 mDvrDataManager = tvSingletons.getDvrDataManager(); 108 } else { 109 mDvrDataManager = null; 110 } 111 mRecommender = recommender; 112 mMinCount = minCount; 113 mMaxCount = maxCount; 114 setHasStableIds(true); 115 } 116 117 @Override 118 public int getItemViewType(int position) { 119 return getItemList().get(position).getLayoutId(); 120 } 121 122 @Override 123 protected int getLayoutResId(int viewType) { 124 return viewType; 125 } 126 127 @Override 128 public long getItemId(int position) { 129 return getItemList().get(position).getItemId(); 130 } 131 132 @Override 133 public void onBindViewHolder(MyViewHolder viewHolder, int position) { 134 int viewType = getItemViewType(position); 135 if (viewType == R.layout.menu_card_guide) { 136 viewHolder.itemView.setOnClickListener(mGuideOnClickListener); 137 } else if (viewType == R.layout.menu_card_setup) { 138 viewHolder.itemView.setOnClickListener(mSetupOnClickListener); 139 } else if (viewType == R.layout.menu_card_app_link) { 140 viewHolder.itemView.setOnClickListener(mAppLinkOnClickListener); 141 } else if (viewType == R.layout.menu_card_dvr) { 142 viewHolder.itemView.setOnClickListener(mDvrOnClickListener); 143 SimpleCardView view = (SimpleCardView) viewHolder.itemView; 144 view.setText(R.string.channels_item_dvr); 145 } else { 146 viewHolder.itemView.setTag(getItemList().get(position).getChannel()); 147 viewHolder.itemView.setOnClickListener(mChannelOnClickListener); 148 } 149 super.onBindViewHolder(viewHolder, position); 150 } 151 152 @Override 153 public void update() { 154 if (getItemCount() == 0) { 155 createItems(); 156 } else { 157 updateItems(); 158 } 159 } 160 161 private void createItems() { 162 List<ChannelsRowItem> items = new ArrayList<>(); 163 items.add(ChannelsRowItem.GUIDE_ITEM); 164 if (needToShowSetupItem()) { 165 items.add(ChannelsRowItem.SETUP_ITEM); 166 } 167 if (needToShowDvrItem()) { 168 items.add(ChannelsRowItem.DVR_ITEM); 169 } 170 if (needToShowAppLinkItem()) { 171 ChannelsRowItem.APP_LINK_ITEM.setChannel( 172 new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build()); 173 items.add(ChannelsRowItem.APP_LINK_ITEM); 174 } 175 for (Channel channel : getRecentChannels()) { 176 items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); 177 } 178 setItemList(items); 179 } 180 181 private void updateItems() { 182 List<ChannelsRowItem> items = getItemList(); 183 // The current index of the item list to iterate. It starts from 1 because the first item 184 // (GUIDE) is always visible and not updated. 185 int currentIndex = 1; 186 if (updateItem(needToShowSetupItem(), ChannelsRowItem.SETUP_ITEM, currentIndex)) { 187 ++currentIndex; 188 } 189 if (updateItem(needToShowDvrItem(), ChannelsRowItem.DVR_ITEM, currentIndex)) { 190 ++currentIndex; 191 } 192 if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) { 193 if (!getMainActivity() 194 .getCurrentChannel() 195 .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) { 196 ChannelsRowItem.APP_LINK_ITEM.setChannel( 197 new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build()); 198 notifyItemChanged(currentIndex); 199 } 200 ++currentIndex; 201 } 202 int numOldChannels = items.size() - currentIndex; 203 if (numOldChannels > 0) { 204 while (items.size() > currentIndex) { 205 items.remove(items.size() - 1); 206 } 207 notifyItemRangeRemoved(currentIndex, numOldChannels); 208 } 209 for (Channel channel : getRecentChannels()) { 210 items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); 211 } 212 int numNewChannels = items.size() - currentIndex; 213 if (numNewChannels > 0) { 214 notifyItemRangeInserted(currentIndex, numNewChannels); 215 } 216 } 217 218 /** Returns {@code true} if the item should be shown. */ 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 TvSingletons singletons = TvSingletons.getSingletons(mContext); 234 TvInputManagerHelper inputManager = singletons.getTvInputManagerHelper(); 235 return singletons.getSetupUtils().hasNewInput(inputManager); 236 } 237 238 private boolean needToShowDvrItem() { 239 TvInputManagerHelper inputManager = 240 TvSingletons.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 TvSingletons.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 } 285 addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId); 286 } 287 return channelList; 288 } 289 290 private static boolean addChannelToList( 291 List<Channel> channelList, Channel channel, long currentChannelId) { 292 if (channel == null 293 || channel.getId() == currentChannelId 294 || channelList.contains(channel) 295 || !channel.isBrowsable()) { 296 return false; 297 } 298 channelList.add(channel); 299 return true; 300 } 301 } 302