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