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