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