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