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.parentalcontrols;
18 
19 import android.database.ContentObserver;
20 import android.media.tv.TvContract;
21 import android.net.Uri;
22 import android.os.Build.VERSION;
23 import android.os.Build.VERSION_CODES;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import androidx.leanback.widget.VerticalGridView;
27 import android.view.KeyEvent;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.TextView;
32 import com.android.tv.R;
33 import com.android.tv.data.ChannelNumber;
34 import com.android.tv.data.api.Channel;
35 import com.android.tv.recommendation.ChannelPreviewUpdater;
36 import com.android.tv.ui.OnRepeatedKeyInterceptListener;
37 import com.android.tv.ui.sidepanel.ActionItem;
38 import com.android.tv.ui.sidepanel.ChannelCheckItem;
39 import com.android.tv.ui.sidepanel.DividerItem;
40 import com.android.tv.ui.sidepanel.Item;
41 import com.android.tv.ui.sidepanel.SideFragment;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 
46 public class ChannelsBlockedFragment extends SideFragment {
47     private static final String TRACKER_LABEL = "Channels blocked";
48     private int mBlockedChannelCount;
49     private final List<Channel> mChannels = new ArrayList<>();
50     private long mLastFocusedChannelId = Channel.INVALID_ID;
51     private int mSelectedPosition = INVALID_POSITION;
52     private boolean mUpdated;
53     private final ContentObserver mProgramUpdateObserver =
54             new ContentObserver(new Handler()) {
55                 @Override
56                 public void onChange(boolean selfChange, Uri uri) {
57                     notifyItemsChanged();
58                 }
59             };
60     private final Item mLockAllItem = new BlockAllItem();
61     private final List<Item> mItems = new ArrayList<>();
62 
63     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)64     public View onCreateView(
65             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
66         View view = super.onCreateView(inflater, container, savedInstanceState);
67         if (mSelectedPosition != INVALID_POSITION) {
68             setSelectedPosition(mSelectedPosition);
69         }
70         VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
71         listView.setOnKeyInterceptListener(
72                 new OnRepeatedKeyInterceptListener(listView) {
73                     @Override
74                     public boolean onInterceptKeyEvent(KeyEvent event) {
75                         // In order to send tune operation once for continuous channel up/down
76                         // events,
77                         // we only call the moveToChannel method on ACTION_UP event of channel
78                         // switch keys.
79                         if (event.getAction() == KeyEvent.ACTION_UP) {
80                             switch (event.getKeyCode()) {
81                                 case KeyEvent.KEYCODE_DPAD_UP:
82                                 case KeyEvent.KEYCODE_DPAD_DOWN:
83                                     if (mLastFocusedChannelId != Channel.INVALID_ID) {
84                                         getMainActivity()
85                                                 .tuneToChannel(
86                                                         getChannelDataManager()
87                                                                 .getChannel(mLastFocusedChannelId));
88                                     }
89                                     break;
90                             }
91                         }
92                         return super.onInterceptKeyEvent(event);
93                     }
94                 });
95         getActivity()
96                 .getContentResolver()
97                 .registerContentObserver(
98                         TvContract.Programs.CONTENT_URI, true, mProgramUpdateObserver);
99         getMainActivity().startShrunkenTvView(true, true);
100         mUpdated = false;
101         return view;
102     }
103 
104     @Override
onDestroyView()105     public void onDestroyView() {
106         getActivity().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
107         getChannelDataManager().applyUpdatedValuesToDb();
108         getMainActivity().endShrunkenTvView();
109         if (VERSION.SDK_INT >= VERSION_CODES.O && mUpdated) {
110             ChannelPreviewUpdater.getInstance(getMainActivity())
111                     .updatePreviewDataForChannelsImmediately();
112         }
113         super.onDestroyView();
114     }
115 
116     @Override
getTitle()117     protected String getTitle() {
118         return getString(R.string.option_channels_locked);
119     }
120 
121     @Override
getTrackerLabel()122     public String getTrackerLabel() {
123         return TRACKER_LABEL;
124     }
125 
126     @Override
getItemList()127     protected List<Item> getItemList() {
128         mItems.clear();
129         mItems.add(mLockAllItem);
130         mChannels.clear();
131         mChannels.addAll(getChannelDataManager().getChannelList());
132         Collections.sort(
133                 mChannels,
134                 (Channel lhs, Channel rhs) -> {
135                     if (lhs.isBrowsable() != rhs.isBrowsable()) {
136                         return lhs.isBrowsable() ? -1 : 1;
137                     }
138                     return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
139                 });
140 
141         final long currentChannelId = getMainActivity().getCurrentChannelId();
142         boolean hasHiddenChannels = false;
143         for (Channel channel : mChannels) {
144             if (!channel.isBrowsable() && !hasHiddenChannels) {
145                 mItems.add(new DividerItem(getString(R.string.option_channels_subheader_hidden)));
146                 hasHiddenChannels = true;
147             }
148             mItems.add(new ChannelBlockedItem(channel));
149             if (channel.isLocked()) {
150                 ++mBlockedChannelCount;
151             }
152             if (channel.getId() == currentChannelId) {
153                 mSelectedPosition = mItems.size() - 1;
154             }
155         }
156         return mItems;
157     }
158 
159     private class BlockAllItem extends ActionItem {
160         private TextView mTextView;
161 
BlockAllItem()162         public BlockAllItem() {
163             super(null);
164         }
165 
166         @Override
onBind(View view)167         protected void onBind(View view) {
168             super.onBind(view);
169             mTextView = (TextView) view.findViewById(R.id.title);
170         }
171 
172         @Override
onUpdate()173         protected void onUpdate() {
174             super.onUpdate();
175             updateText();
176         }
177 
178         @Override
onUnbind()179         protected void onUnbind() {
180             super.onUnbind();
181             mTextView = null;
182         }
183 
184         @Override
onSelected()185         protected void onSelected() {
186             boolean lock = !areAllChannelsBlocked();
187             for (Channel channel : mChannels) {
188                 getChannelDataManager().updateLocked(channel.getId(), lock);
189             }
190             mBlockedChannelCount = lock ? mChannels.size() : 0;
191             notifyItemsChanged();
192             mUpdated = true;
193         }
194 
195         @Override
onFocused()196         protected void onFocused() {
197             super.onFocused();
198             mLastFocusedChannelId = Channel.INVALID_ID;
199         }
200 
updateText()201         private void updateText() {
202             mTextView.setText(
203                     getString(
204                             areAllChannelsBlocked()
205                                     ? R.string.option_channels_unlock_all
206                                     : R.string.option_channels_lock_all));
207         }
208 
areAllChannelsBlocked()209         private boolean areAllChannelsBlocked() {
210             return mBlockedChannelCount == mChannels.size();
211         }
212     }
213 
214     private class ChannelBlockedItem extends ChannelCheckItem {
ChannelBlockedItem(Channel channel)215         private ChannelBlockedItem(Channel channel) {
216             super(channel, getChannelDataManager(), getProgramDataManager());
217         }
218 
219         @Override
getResourceId()220         protected int getResourceId() {
221             return R.layout.option_item_channel_lock;
222         }
223 
224         @Override
onUpdate()225         protected void onUpdate() {
226             super.onUpdate();
227             setChecked(getChannel().isLocked());
228         }
229 
230         @Override
onSelected()231         protected void onSelected() {
232             super.onSelected();
233             getChannelDataManager().updateLocked(getChannel().getId(), isChecked());
234             mBlockedChannelCount += isChecked() ? 1 : -1;
235             notifyItemChanged(mLockAllItem);
236             mUpdated = true;
237         }
238 
239         @Override
onFocused()240         protected void onFocused() {
241             super.onFocused();
242             mLastFocusedChannelId = getChannel().getId();
243         }
244     }
245 }
246