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 17package com.android.tv.ui.sidepanel; 18 19import android.media.tv.TvContract.Channels; 20import android.os.Bundle; 21import android.support.v17.leanback.widget.VerticalGridView; 22import android.view.KeyEvent; 23import android.view.LayoutInflater; 24import android.view.View; 25import android.view.ViewGroup; 26import android.widget.TextView; 27 28import com.android.tv.MainActivity; 29import com.android.tv.R; 30import com.android.tv.data.Channel; 31import com.android.tv.data.ChannelNumber; 32import com.android.tv.ui.OnRepeatedKeyInterceptListener; 33import com.android.tv.util.TvInputManagerHelper; 34import com.android.tv.util.Utils; 35 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.Comparator; 39import java.util.List; 40import java.util.Iterator; 41 42public class CustomizeChannelListFragment extends SideFragment { 43 private static final int GROUP_BY_SOURCE = 0; 44 private static final int GROUP_BY_HD_SD = 1; 45 private static final String TRACKER_LABEL = "customize channel list"; 46 47 private final List<Channel> mChannels = new ArrayList<>(); 48 private final long mInitialChannelId; 49 50 private long mLastFocusedChannelId = Channel.INVALID_ID; 51 52 private int mGroupingType = GROUP_BY_SOURCE; 53 private TvInputManagerHelper mInputManager; 54 private Channel.DefaultComparator mChannelComparator; 55 private boolean mGroupByFragmentRunning; 56 57 private final List<Item> mItems = new ArrayList<>(); 58 59 public CustomizeChannelListFragment() { 60 this(Channel.INVALID_ID); 61 } 62 63 public CustomizeChannelListFragment(long initialChannelId) { 64 mInitialChannelId = initialChannelId; 65 } 66 67 @Override 68 public void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 mInputManager = getMainActivity().getTvInputManagerHelper(); 71 mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager); 72 } 73 74 @Override 75 public View onCreateView(LayoutInflater inflater, ViewGroup container, 76 Bundle savedInstanceState) { 77 View view = super.onCreateView(inflater, container, savedInstanceState); 78 VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list); 79 listView.setOnKeyInterceptListener(new OnRepeatedKeyInterceptListener(listView) { 80 @Override 81 public boolean onInterceptKeyEvent(KeyEvent event) { 82 // In order to send tune operation once for continuous channel up/down events, 83 // we only call the moveToChannel method on ACTION_UP event of channel switch keys. 84 if (event.getAction() == KeyEvent.ACTION_UP) { 85 switch (event.getKeyCode()) { 86 case KeyEvent.KEYCODE_DPAD_UP: 87 case KeyEvent.KEYCODE_DPAD_DOWN: 88 if (mLastFocusedChannelId != Channel.INVALID_ID) { 89 getMainActivity().tuneToChannel( 90 getChannelDataManager().getChannel(mLastFocusedChannelId)); 91 } 92 break; 93 } 94 } 95 return super.onInterceptKeyEvent(event); 96 } 97 }); 98 99 if (!mGroupByFragmentRunning) { 100 getMainActivity().startShrunkenTvView(false, true); 101 102 int initialChannelPosition = INVALID_POSITION; 103 int i = 0; 104 for (Item item : mItems) { 105 if (item instanceof ChannelItem 106 && ((ChannelItem) item).getChannel().getId() == mInitialChannelId) { 107 initialChannelPosition = i; 108 break; 109 } 110 ++i; 111 } 112 if (initialChannelPosition != INVALID_POSITION) { 113 setSelectedPosition(initialChannelPosition); 114 } else { 115 setSelectedPosition(0); 116 } 117 mLastFocusedChannelId = mInitialChannelId; 118 MainActivity tvActivity = getMainActivity(); 119 if (mLastFocusedChannelId != Channel.INVALID_ID && 120 mLastFocusedChannelId != tvActivity.getCurrentChannelId()) { 121 tvActivity.tuneToChannel(getChannelDataManager().getChannel(mLastFocusedChannelId)); 122 } 123 } 124 mGroupByFragmentRunning = false; 125 return view; 126 } 127 128 @Override 129 public void onDestroyView() { 130 getChannelDataManager().applyUpdatedValuesToDb(); 131 if (!mGroupByFragmentRunning) { 132 getMainActivity().endShrunkenTvView(); 133 } 134 super.onDestroyView(); 135 } 136 137 @Override 138 protected String getTitle() { 139 return getString(R.string.side_panel_title_edit_channels_for_an_input); 140 } 141 142 @Override 143 public String getTrackerLabel() { 144 return TRACKER_LABEL; 145 } 146 147 @Override 148 protected List<Item> getItemList() { 149 mItems.clear(); 150 mChannels.clear(); 151 mChannels.addAll(getChannelDataManager().getChannelList()); 152 if (mGroupingType == GROUP_BY_SOURCE) { 153 addItemForGroupBySource(mItems); 154 } else { 155 // GROUP_BY_HD_SD 156 addItemForGroupByHdSd(mItems); 157 } 158 return mItems; 159 } 160 161 private void cleanUpOneChannelGroupItem(List<Item> items) { 162 Iterator<Item> iter = items.iterator(); 163 while (iter.hasNext()) { 164 Item item = iter.next(); 165 if (item instanceof SelectGroupItem) { 166 SelectGroupItem selectGroupItem = (SelectGroupItem) item; 167 if (selectGroupItem.mChannelItemsInGroup.size() == 1) { 168 ((ChannelItem) selectGroupItem.mChannelItemsInGroup.get(0)) 169 .mSelectGroupItem = null; 170 iter.remove(); 171 } 172 } 173 } 174 } 175 176 private void addItemForGroupBySource(List<Item> items) { 177 items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_sources))); 178 SelectGroupItem selectGroupItem = null; 179 ArrayList<Channel> channels = new ArrayList<>(mChannels); 180 Collections.sort(channels, mChannelComparator); 181 182 String inputId = null; 183 for (Channel channel: channels) { 184 if (!channel.getInputId().equals(inputId)) { 185 inputId = channel.getInputId(); 186 String inputLabel = Utils.loadLabel(getActivity(), 187 mInputManager.getTvInputInfo(inputId)); 188 items.add(new DividerItem(inputLabel)); 189 selectGroupItem = new SelectGroupItem(); 190 items.add(selectGroupItem); 191 } 192 ChannelItem channelItem = new ChannelItem(channel, selectGroupItem); 193 items.add(channelItem); 194 selectGroupItem.addChannelItem(channelItem); 195 } 196 cleanUpOneChannelGroupItem(items); 197 } 198 199 private void addItemForGroupByHdSd(List<Item> items) { 200 items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_hd_sd))); 201 SelectGroupItem selectGroupItem = null; 202 ArrayList<Channel> channels = new ArrayList<>(mChannels); 203 Collections.sort(channels, new Comparator<Channel>() { 204 @Override 205 public int compare(Channel lhs, Channel rhs) { 206 boolean lhsHd = isHdChannel(lhs); 207 boolean rhsHd = isHdChannel(rhs); 208 if (lhsHd == rhsHd) { 209 return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); 210 } else { 211 return lhsHd ? -1 : 1; 212 } 213 } 214 }); 215 216 Boolean isHdGroup = null; 217 for (Channel channel: channels) { 218 boolean isHd = isHdChannel(channel); 219 if (isHdGroup == null || isHd != isHdGroup) { 220 isHdGroup = isHd; 221 items.add(new DividerItem(isHd 222 ? getString(R.string.edit_channels_group_divider_for_hd) 223 : getString(R.string.edit_channels_group_divider_for_sd))); 224 selectGroupItem = new SelectGroupItem(); 225 items.add(selectGroupItem); 226 } 227 ChannelItem channelItem = new ChannelItem(channel, selectGroupItem); 228 items.add(channelItem); 229 selectGroupItem.addChannelItem(channelItem); 230 } 231 cleanUpOneChannelGroupItem(items); 232 } 233 234 private static boolean isHdChannel(Channel channel) { 235 String videoFormat = channel.getVideoFormat(); 236 return videoFormat != null && 237 (Channels.VIDEO_FORMAT_720P.equals(videoFormat) 238 || Channels.VIDEO_FORMAT_1080I.equals(videoFormat) 239 || Channels.VIDEO_FORMAT_1080P.equals(videoFormat) 240 || Channels.VIDEO_FORMAT_2160P.equals(videoFormat) 241 || Channels.VIDEO_FORMAT_4320P.equals(videoFormat)); 242 } 243 244 private class SelectGroupItem extends ActionItem { 245 private final List<ChannelItem> mChannelItemsInGroup = new ArrayList<>(); 246 private TextView mTextView; 247 private boolean mAllChecked; 248 249 public SelectGroupItem() { 250 super(null); 251 } 252 253 private void addChannelItem(ChannelItem channelItem) { 254 mChannelItemsInGroup.add(channelItem); 255 } 256 257 @Override 258 protected void onBind(View view) { 259 super.onBind(view); 260 mTextView = (TextView) view.findViewById(R.id.title); 261 } 262 263 @Override 264 protected void onUpdate() { 265 super.onUpdate(); 266 mAllChecked = true; 267 for (ChannelItem channelItem : mChannelItemsInGroup) { 268 if (!channelItem.getChannel().isBrowsable()) { 269 mAllChecked = false; 270 break; 271 } 272 } 273 mTextView.setText(getString(mAllChecked 274 ? R.string.edit_channels_item_deselect_group 275 : R.string.edit_channels_item_select_group)); 276 } 277 278 @Override 279 protected void onSelected() { 280 for (ChannelItem channelItem : mChannelItemsInGroup) { 281 Channel channel = channelItem.getChannel(); 282 if (channel.isBrowsable() == mAllChecked) { 283 getChannelDataManager().updateBrowsable(channel.getId(), !mAllChecked, true); 284 channelItem.notifyUpdated(); 285 } 286 } 287 getChannelDataManager().notifyChannelBrowsableChanged(); 288 mAllChecked = !mAllChecked; 289 mTextView.setText(getString(mAllChecked 290 ? R.string.edit_channels_item_deselect_group 291 : R.string.edit_channels_item_select_group)); 292 } 293 } 294 295 private class ChannelItem extends ChannelCheckItem { 296 private SelectGroupItem mSelectGroupItem; 297 298 public ChannelItem(Channel channel, SelectGroupItem selectGroupItem) { 299 super(channel, getChannelDataManager(), getProgramDataManager()); 300 mSelectGroupItem = selectGroupItem; 301 } 302 303 @Override 304 protected void onUpdate() { 305 super.onUpdate(); 306 setChecked(getChannel().isBrowsable()); 307 } 308 309 @Override 310 protected void onSelected() { 311 super.onSelected(); 312 getChannelDataManager().updateBrowsable(getChannel().getId(), isChecked()); 313 if (mSelectGroupItem != null) { 314 mSelectGroupItem.notifyUpdated(); 315 } 316 } 317 318 @Override 319 protected void onFocused() { 320 super.onFocused(); 321 mLastFocusedChannelId = getChannel().getId(); 322 } 323 } 324 325 private class GroupBySubMenu extends SubMenuItem { 326 private static final String TRACKER_LABEL = "Group by"; 327 public GroupBySubMenu(String description) { 328 super(getString(R.string.edit_channels_item_group_by), description, 329 getMainActivity().getOverlayManager().getSideFragmentManager()); 330 } 331 332 @Override 333 protected SideFragment getFragment() { 334 return new SideFragment() { 335 @Override 336 protected String getTitle() { 337 return getString(R.string.side_panel_title_group_by); 338 } 339 @Override 340 public String getTrackerLabel() { 341 return GroupBySubMenu.TRACKER_LABEL; 342 } 343 344 @Override 345 protected List<Item> getItemList() { 346 List<Item> items = new ArrayList<>(); 347 items.add(new RadioButtonItem( 348 getString(R.string.edit_channels_group_by_sources)) { 349 @Override 350 protected void onSelected() { 351 super.onSelected(); 352 mGroupingType = GROUP_BY_SOURCE; 353 closeFragment(); 354 } 355 }); 356 items.add(new RadioButtonItem( 357 getString(R.string.edit_channels_group_by_hd_sd)) { 358 @Override 359 protected void onSelected() { 360 super.onSelected(); 361 mGroupingType = GROUP_BY_HD_SD; 362 closeFragment(); 363 } 364 }); 365 ((RadioButtonItem) items.get(mGroupingType)).setChecked(true); 366 return items; 367 } 368 }; 369 } 370 371 @Override 372 protected void onSelected() { 373 mGroupByFragmentRunning = true; 374 super.onSelected(); 375 } 376 } 377} 378