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