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.parental;
18
19import android.content.Context;
20import android.media.tv.TvContentRating;
21import android.media.tv.TvInputManager;
22
23import com.android.tv.experiments.Experiments;
24import com.android.tv.parental.ContentRatingSystem.Rating;
25import com.android.tv.parental.ContentRatingSystem.SubRating;
26import com.android.tv.util.TvSettings;
27import com.android.tv.util.TvSettings.ContentRatingLevel;
28
29import java.util.HashSet;
30import java.util.Set;
31
32public class ParentalControlSettings {
33    /**
34     * The rating and all of its sub-ratings are blocked.
35     */
36    public static final int RATING_BLOCKED = 0;
37
38    /**
39     * The rating is blocked but not all of its sub-ratings are blocked.
40     */
41    public static final int RATING_BLOCKED_PARTIAL = 1;
42
43    /**
44     * The rating is not blocked.
45     */
46    public static final int RATING_NOT_BLOCKED = 2;
47
48    private final Context mContext;
49    private final TvInputManager mTvInputManager;
50
51    // mRatings is expected to be synchronized with mTvInputManager.getBlockedRatings().
52    private Set<TvContentRating> mRatings;
53    private Set<TvContentRating> mCustomRatings;
54
55    public ParentalControlSettings(Context context) {
56        mContext = context;
57        mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
58    }
59
60    public boolean isParentalControlsEnabled() {
61        return mTvInputManager.isParentalControlsEnabled();
62    }
63
64    public void setParentalControlsEnabled(boolean enabled) {
65        mTvInputManager.setParentalControlsEnabled(enabled);
66    }
67
68    public void setContentRatingSystemEnabled(ContentRatingsManager manager,
69            ContentRatingSystem contentRatingSystem, boolean enabled) {
70        if (enabled) {
71            TvSettings.addContentRatingSystem(mContext, contentRatingSystem.getId());
72
73            // Ensure newly added system has ratings for current level set
74            updateRatingsForCurrentLevel(manager);
75        } else {
76            // Ensure no ratings are blocked for the selected rating system
77            for (TvContentRating tvContentRating : mTvInputManager.getBlockedRatings()) {
78                if (contentRatingSystem.ownsRating(tvContentRating)) {
79                    mTvInputManager.removeBlockedRating(tvContentRating);
80                }
81            }
82
83            TvSettings.removeContentRatingSystem(mContext, contentRatingSystem.getId());
84        }
85    }
86
87    public boolean isContentRatingSystemEnabled(ContentRatingSystem contentRatingSystem) {
88        return TvSettings.hasContentRatingSystem(mContext, contentRatingSystem.getId());
89    }
90
91    public void loadRatings() {
92        mRatings = new HashSet<>(mTvInputManager.getBlockedRatings());
93    }
94
95    private void storeRatings() {
96        Set<TvContentRating> removed = new HashSet<>(mTvInputManager.getBlockedRatings());
97        removed.removeAll(mRatings);
98        for (TvContentRating tvContentRating : removed) {
99            mTvInputManager.removeBlockedRating(tvContentRating);
100        }
101
102        Set<TvContentRating> added = new HashSet<>(mRatings);
103        added.removeAll(mTvInputManager.getBlockedRatings());
104        for (TvContentRating tvContentRating : added) {
105            mTvInputManager.addBlockedRating(tvContentRating);
106        }
107    }
108
109    private void updateRatingsForCurrentLevel(ContentRatingsManager manager) {
110        @ContentRatingLevel int currentLevel = getContentRatingLevel();
111        if (currentLevel != TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
112            mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, currentLevel);
113            if (currentLevel != TvSettings.CONTENT_RATING_LEVEL_NONE) {
114                // UNRATED contents should be blocked unless the rating level is none or custom
115                mRatings.add(TvContentRating.UNRATED);
116            }
117            storeRatings();
118        }
119    }
120
121    public void setContentRatingLevel(ContentRatingsManager manager,
122            @ContentRatingLevel int level) {
123        @ContentRatingLevel int currentLevel = getContentRatingLevel();
124        if (level == currentLevel) {
125            return;
126        }
127        if (currentLevel == TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
128            mCustomRatings = mRatings;
129        }
130        TvSettings.setContentRatingLevel(mContext, level);
131        if (level == TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
132            if (mCustomRatings != null) {
133                mRatings = new HashSet<>(mCustomRatings);
134            }
135        } else {
136            mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, level);
137            if (level != TvSettings.CONTENT_RATING_LEVEL_NONE
138                    && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
139                // UNRATED contents should be blocked unless the rating level is none or custom
140                mRatings.add(TvContentRating.UNRATED);
141            }
142        }
143        storeRatings();
144    }
145
146    @ContentRatingLevel
147    public int getContentRatingLevel() {
148        return TvSettings.getContentRatingLevel(mContext);
149    }
150
151    /** Sets the blocked status of a unrated contents. */
152    public boolean setUnratedBlocked(boolean blocked) {
153        boolean changed;
154        if (blocked) {
155            changed = mRatings.add(TvContentRating.UNRATED);
156            mTvInputManager.addBlockedRating(TvContentRating.UNRATED);
157        } else {
158            changed = mRatings.remove(TvContentRating.UNRATED);
159            mTvInputManager.removeBlockedRating(TvContentRating.UNRATED);
160        }
161        if (changed) {
162            // change to custom level if the blocked status is changed
163            changeToCustomLevel();
164        }
165        return changed;
166    }
167
168    /**
169     * Sets the blocked status of a given content rating.
170     * <p>
171     * Note that a call to this method automatically changes the current rating level to
172     * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
173     * </p>
174     *
175     * @param contentRatingSystem The content rating system where the given rating belongs.
176     * @param rating The content rating to set.
177     * @return {@code true} if changed, {@code false} otherwise.
178     * @see #setSubRatingBlocked
179     */
180    public boolean setRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating,
181            boolean blocked) {
182        return setRatingBlockedInternal(contentRatingSystem, rating, null, blocked);
183    }
184
185    /**
186     * Checks whether any of given ratings is blocked.
187     *
188     * @param ratings The array of ratings to check
189     * @return {@code true} if a rating is blocked, {@code false} otherwise.
190     */
191    public boolean isRatingBlocked(TvContentRating[] ratings) {
192        return getBlockedRating(ratings) != null;
193    }
194
195    /**
196     * Checks whether any of given ratings is blocked and returns the first blocked rating.
197     *
198     * @param ratings The array of ratings to check
199     * @return The {@link TvContentRating} that is blocked.
200     */
201    public TvContentRating getBlockedRating(TvContentRating[] ratings) {
202        if (ratings == null || ratings.length <= 0) {
203            return mTvInputManager.isRatingBlocked(TvContentRating.UNRATED)
204                    ? TvContentRating.UNRATED
205                    : null;
206        }
207        for (TvContentRating rating : ratings) {
208            if (mTvInputManager.isRatingBlocked(rating)) {
209                return rating;
210            }
211        }
212        return null;
213    }
214
215    /**
216     * Checks whether a given rating is blocked by the user or not.
217     *
218     * @param contentRatingSystem The content rating system where the given rating belongs.
219     * @param rating The content rating to check.
220     * @return {@code true} if blocked, {@code false} otherwise.
221     */
222    public boolean isRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating) {
223        return mRatings.contains(toTvContentRating(contentRatingSystem, rating));
224    }
225
226    /**
227     * Sets the blocked status of a given content sub-rating.
228     * <p>
229     * Note that a call to this method automatically changes the current rating level to
230     * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
231     * </p>
232     *
233     * @param contentRatingSystem The content rating system where the given rating belongs.
234     * @param rating The content rating associated with the given sub-rating.
235     * @param subRating The content sub-rating to set.
236     * @return {@code true} if changed, {@code false} otherwise.
237     * @see #setRatingBlocked
238     */
239    public boolean setSubRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating,
240            SubRating subRating, boolean blocked) {
241        return setRatingBlockedInternal(contentRatingSystem, rating, subRating, blocked);
242    }
243
244    /**
245     * Checks whether a given content sub-rating is blocked by the user or not.
246     *
247     * @param contentRatingSystem The content rating system where the given rating belongs.
248     * @param rating The content rating associated with the given sub-rating.
249     * @param subRating The content sub-rating to check.
250     * @return {@code true} if blocked, {@code false} otherwise.
251     */
252    public boolean isSubRatingEnabled(ContentRatingSystem contentRatingSystem, Rating rating,
253            SubRating subRating) {
254        return mRatings.contains(toTvContentRating(contentRatingSystem, rating, subRating));
255    }
256
257    private boolean setRatingBlockedInternal(ContentRatingSystem contentRatingSystem, Rating rating,
258            SubRating subRating, boolean blocked) {
259        TvContentRating tvContentRating = (subRating == null)
260                ? toTvContentRating(contentRatingSystem, rating)
261                : toTvContentRating(contentRatingSystem, rating, subRating);
262        boolean changed;
263        if (blocked) {
264            changed = mRatings.add(tvContentRating);
265            mTvInputManager.addBlockedRating(tvContentRating);
266        } else {
267            changed = mRatings.remove(tvContentRating);
268            mTvInputManager.removeBlockedRating(tvContentRating);
269        }
270        if (changed) {
271            changeToCustomLevel();
272        }
273        return changed;
274    }
275
276    private void changeToCustomLevel() {
277        if (getContentRatingLevel() != TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
278            TvSettings.setContentRatingLevel(mContext, TvSettings.CONTENT_RATING_LEVEL_CUSTOM);
279        }
280    }
281
282    /**
283     * Returns the blocked status of a given rating. The status can be one of the followings:
284     * {@link #RATING_BLOCKED}, {@link #RATING_BLOCKED_PARTIAL} and {@link #RATING_NOT_BLOCKED}
285     */
286    public int getBlockedStatus(ContentRatingSystem contentRatingSystem, Rating rating) {
287        if (isRatingBlocked(contentRatingSystem, rating)) {
288            return RATING_BLOCKED;
289        }
290        for (SubRating subRating : rating.getSubRatings()) {
291            if (isSubRatingEnabled(contentRatingSystem, rating, subRating)) {
292                return RATING_BLOCKED_PARTIAL;
293            }
294        }
295        return RATING_NOT_BLOCKED;
296    }
297
298    private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem,
299            Rating rating) {
300        return TvContentRating.createRating(contentRatingSystem.getDomain(),
301                contentRatingSystem.getName(), rating.getName());
302    }
303
304    private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem,
305            Rating rating, SubRating subRating) {
306        return TvContentRating.createRating(contentRatingSystem.getDomain(),
307                contentRatingSystem.getName(), rating.getName(), subRating.getName());
308    }
309}
310