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.graphics.drawable.Drawable; 20 import android.media.tv.TvContentRating; 21 import android.os.Bundle; 22 import android.util.ArrayMap; 23 import android.util.SparseIntArray; 24 import android.view.View; 25 import android.widget.CompoundButton; 26 import android.widget.ImageView; 27 import com.android.tv.MainActivity; 28 import com.android.tv.R; 29 import com.android.tv.common.experiments.Experiments; 30 import com.android.tv.dialog.WebDialogFragment; 31 import com.android.tv.license.LicenseUtils; 32 import com.android.tv.parental.ContentRatingSystem; 33 import com.android.tv.parental.ContentRatingSystem.Rating; 34 import com.android.tv.parental.ParentalControlSettings; 35 import com.android.tv.ui.sidepanel.CheckBoxItem; 36 import com.android.tv.ui.sidepanel.DividerItem; 37 import com.android.tv.ui.sidepanel.Item; 38 import com.android.tv.ui.sidepanel.RadioButtonItem; 39 import com.android.tv.ui.sidepanel.SideFragment; 40 import com.android.tv.util.TvSettings; 41 import com.android.tv.util.TvSettings.ContentRatingLevel; 42 import com.google.common.collect.ImmutableList; 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.List; 46 import java.util.Map; 47 48 public class RatingsFragment extends SideFragment { 49 private static final SparseIntArray sLevelResourceIdMap; 50 private static final SparseIntArray sDescriptionResourceIdMap; 51 private static final String TRACKER_LABEL = "Ratings"; 52 private int mItemsSize; 53 54 static { 55 sLevelResourceIdMap = new SparseIntArray(5); sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, R.string.option_rating_none)56 sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, R.string.option_rating_none); sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high)57 sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high); sLevelResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium)58 sLevelResourceIdMap.put( 59 TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium); sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low)60 sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low); sLevelResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom)61 sLevelResourceIdMap.put( 62 TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom); 63 64 sDescriptionResourceIdMap = new SparseIntArray(sLevelResourceIdMap.size()); sDescriptionResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high_description)65 sDescriptionResourceIdMap.put( 66 TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high_description); sDescriptionResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium_description)67 sDescriptionResourceIdMap.put( 68 TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium_description); sDescriptionResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low_description)69 sDescriptionResourceIdMap.put( 70 TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low_description); sDescriptionResourceIdMap.put( TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom_description)71 sDescriptionResourceIdMap.put( 72 TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom_description); 73 } 74 75 private final List<RatingLevelItem> mRatingLevelItems = new ArrayList<>(); 76 // A map from the rating system ID string to RatingItem objects. 77 private final Map<String, List<RatingItem>> mContentRatingSystemItemMap = new ArrayMap<>(); 78 private CheckBoxItem mBlockUnratedItem; 79 private ParentalControlSettings mParentalControlSettings; 80 getDescription(MainActivity tvActivity)81 public static String getDescription(MainActivity tvActivity) { 82 @ContentRatingLevel 83 int currentLevel = tvActivity.getParentalControlSettings().getContentRatingLevel(); 84 if (sLevelResourceIdMap.indexOfKey(currentLevel) >= 0) { 85 return tvActivity.getString(sLevelResourceIdMap.get(currentLevel)); 86 } 87 return null; 88 } 89 90 @Override getTitle()91 protected String getTitle() { 92 return getString(R.string.option_ratings); 93 } 94 95 @Override getTrackerLabel()96 public String getTrackerLabel() { 97 return TRACKER_LABEL; 98 } 99 100 @Override getItemList()101 protected List<Item> getItemList() { 102 List<Item> items = new ArrayList<>(); 103 104 if (mBlockUnratedItem != null 105 && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { 106 items.add(mBlockUnratedItem); 107 items.add(new DividerItem()); 108 } 109 110 mRatingLevelItems.clear(); 111 for (int i = 0; i < sLevelResourceIdMap.size(); ++i) { 112 mRatingLevelItems.add(new RatingLevelItem(sLevelResourceIdMap.keyAt(i))); 113 } 114 updateRatingLevels(); 115 items.addAll(mRatingLevelItems); 116 117 mContentRatingSystemItemMap.clear(); 118 119 List<ContentRatingSystem> contentRatingSystems = 120 getMainActivity().getContentRatingsManager().getContentRatingSystems(); 121 Collections.sort(contentRatingSystems, ContentRatingSystem.DISPLAY_NAME_COMPARATOR); 122 123 for (ContentRatingSystem s : contentRatingSystems) { 124 if (mParentalControlSettings.isContentRatingSystemEnabled(s)) { 125 List<RatingItem> ratingItems = new ArrayList<>(); 126 boolean hasSubRating = false; 127 items.add(new DividerItem(s.getDisplayName())); 128 for (Rating rating : s.getRatings()) { 129 RatingItem item = 130 rating.getSubRatings().isEmpty() 131 ? new RatingItem(s, rating) 132 : new RatingWithSubItem(s, rating); 133 items.add(item); 134 if (rating.getSubRatings().isEmpty()) { 135 ratingItems.add(item); 136 } else { 137 hasSubRating = true; 138 } 139 } 140 // Only include rating systems that don't contain any sub ratings in the map for 141 // simplicity. 142 if (!hasSubRating) { 143 mContentRatingSystemItemMap.put(s.getId(), ratingItems); 144 } 145 } 146 } 147 if (LicenseUtils.hasRatingAttribution(getMainActivity().getAssets())) { 148 // Display the attribution if our content rating system is selected. 149 items.add(new DividerItem()); 150 items.add(new AttributionItem(getMainActivity())); 151 } 152 mItemsSize = items.size(); 153 return items; 154 } 155 156 @Override onCreate(Bundle savedInstanceState)157 public void onCreate(Bundle savedInstanceState) { 158 super.onCreate(savedInstanceState); 159 mParentalControlSettings = getMainActivity().getParentalControlSettings(); 160 mParentalControlSettings.loadRatings(); 161 if (Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { 162 mBlockUnratedItem = 163 new CheckBoxItem( 164 getResources().getString(R.string.option_block_unrated_programs)) { 165 166 @Override 167 protected void onUpdate() { 168 super.onUpdate(); 169 setChecked( 170 mParentalControlSettings.isRatingBlocked( 171 ImmutableList.of(TvContentRating.UNRATED))); 172 } 173 174 @Override 175 protected void onSelected() { 176 super.onSelected(); 177 if (mParentalControlSettings.setUnratedBlocked(isChecked())) { 178 updateRatingLevels(); 179 } 180 } 181 }; 182 } 183 } 184 185 @Override onResume()186 public void onResume() { 187 super.onResume(); 188 // Although we set the attribution item at the end of the item list non-focusable, we do get 189 // its position when the fragment is resumed. This ensures that we do not select the 190 // non-focusable item at the end of the list. See b/17387103. 191 if (getSelectedPosition() >= mItemsSize) { 192 setSelectedPosition(mItemsSize - 1); 193 } 194 } 195 updateRatingLevels()196 private void updateRatingLevels() { 197 @ContentRatingLevel int ratingLevel = mParentalControlSettings.getContentRatingLevel(); 198 for (RatingLevelItem ratingLevelItem : mRatingLevelItems) { 199 ratingLevelItem.setChecked(ratingLevel == ratingLevelItem.mRatingLevel); 200 } 201 } 202 updateDependentRatingItems( ContentRatingSystem.Order order, int selectedRatingOrderIndex, String contentRatingSystemId, boolean isChecked)203 private void updateDependentRatingItems( 204 ContentRatingSystem.Order order, 205 int selectedRatingOrderIndex, 206 String contentRatingSystemId, 207 boolean isChecked) { 208 List<RatingItem> ratingItems = mContentRatingSystemItemMap.get(contentRatingSystemId); 209 if (ratingItems != null) { 210 for (RatingItem item : ratingItems) { 211 int ratingOrderIndex = item.getRatingOrderIndex(order); 212 if (ratingOrderIndex != -1 213 && ((ratingOrderIndex > selectedRatingOrderIndex && isChecked) 214 || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) { 215 item.setRatingBlocked(isChecked); 216 } 217 } 218 } 219 } 220 221 private class RatingLevelItem extends RadioButtonItem { 222 private final int mRatingLevel; 223 RatingLevelItem(int ratingLevel)224 private RatingLevelItem(int ratingLevel) { 225 super( 226 getString(sLevelResourceIdMap.get(ratingLevel)), 227 (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0) 228 ? getString(sDescriptionResourceIdMap.get(ratingLevel)) 229 : null); 230 mRatingLevel = ratingLevel; 231 } 232 233 @Override onSelected()234 protected void onSelected() { 235 super.onSelected(); 236 mParentalControlSettings.setContentRatingLevel( 237 getMainActivity().getContentRatingsManager(), mRatingLevel); 238 if (mBlockUnratedItem != null 239 && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { 240 // set checked if UNRATED is blocked, and set unchecked otherwise. 241 mBlockUnratedItem.setChecked( 242 mParentalControlSettings.isRatingBlocked( 243 ImmutableList.of(TvContentRating.UNRATED))); 244 } 245 notifyItemsChanged(mRatingLevelItems.size()); 246 } 247 } 248 249 private class RatingItem extends CheckBoxItem { 250 protected final ContentRatingSystem mContentRatingSystem; 251 protected final Rating mRating; 252 private final Drawable mIcon; 253 private CompoundButton mCompoundButton; 254 private final List<ContentRatingSystem.Order> mOrders = new ArrayList<>(); 255 private final List<Integer> mOrderIndexes = new ArrayList<>(); 256 RatingItem(ContentRatingSystem contentRatingSystem, Rating rating)257 private RatingItem(ContentRatingSystem contentRatingSystem, Rating rating) { 258 super(rating.getTitle(), rating.getDescription()); 259 mContentRatingSystem = contentRatingSystem; 260 mRating = rating; 261 mIcon = rating.getIcon(); 262 for (ContentRatingSystem.Order order : mContentRatingSystem.getOrders()) { 263 int orderIndex = order.getRatingIndex(mRating); 264 if (orderIndex != -1) { 265 mOrders.add(order); 266 mOrderIndexes.add(orderIndex); 267 } 268 } 269 } 270 271 @Override onBind(View view)272 protected void onBind(View view) { 273 super.onBind(view); 274 275 mCompoundButton = (CompoundButton) view.findViewById(getCompoundButtonId()); 276 mCompoundButton.setVisibility(View.VISIBLE); 277 278 ImageView imageView = (ImageView) view.findViewById(R.id.icon); 279 if (mIcon != null) { 280 imageView.setVisibility(View.VISIBLE); 281 imageView.setImageDrawable(mIcon); 282 } else { 283 imageView.setVisibility(View.GONE); 284 } 285 } 286 287 @Override onUnbind()288 protected void onUnbind() { 289 super.onUnbind(); 290 mCompoundButton = null; 291 } 292 293 @Override onUpdate()294 protected void onUpdate() { 295 super.onUpdate(); 296 mCompoundButton.setButtonDrawable(getButtonDrawable()); 297 setChecked(mParentalControlSettings.isRatingBlocked(mContentRatingSystem, mRating)); 298 } 299 300 @Override onSelected()301 protected void onSelected() { 302 super.onSelected(); 303 if (mParentalControlSettings.setRatingBlocked( 304 mContentRatingSystem, mRating, isChecked())) { 305 updateRatingLevels(); 306 } 307 // Automatically check/uncheck dependent ratings. 308 for (int i = 0; i < mOrders.size(); i++) { 309 updateDependentRatingItems( 310 mOrders.get(i), 311 mOrderIndexes.get(i), 312 mContentRatingSystem.getId(), 313 isChecked()); 314 } 315 } 316 317 @Override getResourceId()318 protected int getResourceId() { 319 return R.layout.option_item_rating; 320 } 321 getButtonDrawable()322 protected int getButtonDrawable() { 323 return R.drawable.btn_lock_material_anim; 324 } 325 getRatingOrderIndex(ContentRatingSystem.Order order)326 private int getRatingOrderIndex(ContentRatingSystem.Order order) { 327 int orderIndex = mOrders.indexOf(order); 328 return orderIndex == -1 ? -1 : mOrderIndexes.get(orderIndex); 329 } 330 setRatingBlocked(boolean isChecked)331 private void setRatingBlocked(boolean isChecked) { 332 if (isChecked() == isChecked) { 333 return; 334 } 335 mParentalControlSettings.setRatingBlocked(mContentRatingSystem, mRating, isChecked); 336 notifyUpdated(); 337 } 338 } 339 340 private class RatingWithSubItem extends RatingItem { RatingWithSubItem(ContentRatingSystem contentRatingSystem, Rating rating)341 private RatingWithSubItem(ContentRatingSystem contentRatingSystem, Rating rating) { 342 super(contentRatingSystem, rating); 343 } 344 345 @Override onSelected()346 protected void onSelected() { 347 getMainActivity() 348 .getOverlayManager() 349 .getSideFragmentManager() 350 .show(SubRatingsFragment.create(mContentRatingSystem, mRating.getName())); 351 } 352 353 @Override getButtonDrawable()354 protected int getButtonDrawable() { 355 int blockedStatus = 356 mParentalControlSettings.getBlockedStatus(mContentRatingSystem, mRating); 357 if (blockedStatus == ParentalControlSettings.RATING_BLOCKED) { 358 return R.drawable.btn_lock_material; 359 } else if (blockedStatus == ParentalControlSettings.RATING_BLOCKED_PARTIAL) { 360 return R.drawable.btn_partial_lock_material; 361 } 362 return R.drawable.btn_unlock_material; 363 } 364 } 365 366 /** Opens a dialog showing the sources of the rating descriptions. */ 367 public static class AttributionItem extends Item { 368 public static final String DIALOG_TAG = AttributionItem.class.getSimpleName(); 369 public static final String TRACKER_LABEL = "Sources for content rating systems"; 370 private final MainActivity mMainActivity; 371 AttributionItem(MainActivity mainActivity)372 public AttributionItem(MainActivity mainActivity) { 373 mMainActivity = mainActivity; 374 } 375 376 @Override getResourceId()377 protected int getResourceId() { 378 return R.layout.option_item_attribution; 379 } 380 381 @Override onSelected()382 protected void onSelected() { 383 WebDialogFragment dialog = 384 WebDialogFragment.newInstance( 385 LicenseUtils.RATING_SOURCE_FILE, 386 mMainActivity.getString(R.string.option_attribution), 387 TRACKER_LABEL); 388 mMainActivity.getOverlayManager().showDialogFragment(DIALOG_TAG, dialog, false); 389 } 390 } 391 } 392