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
17package com.android.tv.ui.sidepanel.parentalcontrols;
18
19import android.graphics.drawable.Drawable;
20import android.os.Bundle;
21import android.util.ArrayMap;
22import android.util.SparseIntArray;
23import android.view.View;
24import android.widget.CompoundButton;
25import android.widget.ImageView;
26
27import com.android.tv.MainActivity;
28import com.android.tv.R;
29import com.android.tv.dialog.WebDialogFragment;
30import com.android.tv.license.LicenseUtils;
31import com.android.tv.parental.ContentRatingSystem;
32import com.android.tv.parental.ContentRatingSystem.Rating;
33import com.android.tv.parental.ParentalControlSettings;
34import com.android.tv.ui.sidepanel.CheckBoxItem;
35import com.android.tv.ui.sidepanel.DividerItem;
36import com.android.tv.ui.sidepanel.Item;
37import com.android.tv.ui.sidepanel.RadioButtonItem;
38import com.android.tv.ui.sidepanel.SideFragment;
39import com.android.tv.util.TvSettings;
40import com.android.tv.util.TvSettings.ContentRatingLevel;
41
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.List;
45import java.util.Map;
46
47public 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 ParentalControlSettings mParentalControlSettings;
81
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
92    protected String getTitle() {
93        return getString(R.string.option_ratings);
94    }
95
96    @Override
97    public String getTrackerLabel() {
98        return TRACKER_LABEL;
99    }
100
101    @Override
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
151    public void onCreate(Bundle savedInstanceState) {
152        super.onCreate(savedInstanceState);
153        mParentalControlSettings = getMainActivity().getParentalControlSettings();
154        mParentalControlSettings.loadRatings();
155    }
156
157    @Override
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
168    private void updateRatingLevels() {
169        @ContentRatingLevel int ratingLevel = mParentalControlSettings.getContentRatingLevel();
170        for (RatingLevelItem ratingLevelItem : mRatingLevelItems) {
171            ratingLevelItem.setChecked(ratingLevel == ratingLevelItem.mRatingLevel);
172        }
173    }
174
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
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
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
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
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
248        protected void onUnbind() {
249            super.onUnbind();
250            mCompoundButton = null;
251        }
252
253        @Override
254        protected void onUpdate() {
255            super.onUpdate();
256            mCompoundButton.setButtonDrawable(getButtonDrawable());
257            setChecked(mParentalControlSettings.isRatingBlocked(mContentRatingSystem, mRating));
258        }
259
260        @Override
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
275        protected int getResourceId() {
276            return R.layout.option_item_rating;
277        }
278
279        protected int getButtonDrawable() {
280            return R.drawable.btn_lock_material_anim;
281        }
282
283        private int getRatingOrderIndex(ContentRatingSystem.Order order) {
284            int orderIndex = mOrders.indexOf(order);
285            return orderIndex == -1 ? -1 : mOrderIndexes.get(orderIndex);
286        }
287
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 {
298        private RatingWithSubItem(ContentRatingSystem contentRatingSystem, Rating rating) {
299            super(contentRatingSystem, rating);
300        }
301
302        @Override
303        protected void onSelected() {
304            getMainActivity().getOverlayManager().getSideFragmentManager()
305                    .show(new SubRatingsFragment(mContentRatingSystem, mRating));
306        }
307
308        @Override
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
329        public AttributionItem(MainActivity mainActivity) {
330            mMainActivity = mainActivity;
331        }
332
333        @Override
334        protected int getResourceId() {
335            return R.layout.option_item_attribution;
336        }
337
338        @Override
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