Home | History | Annotate | Download | only in parentalcontrols
      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