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