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