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