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