• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.parental;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.media.tv.TvContentRating;
22 import android.text.TextUtils;
23 
24 import com.android.tv.R;
25 
26 import java.util.ArrayList;
27 import java.util.Comparator;
28 import java.util.List;
29 import java.util.Locale;
30 
31 public class ContentRatingSystem {
32     /*
33      * A comparator that implements the display order of a group of content rating systems.
34      */
35     public static final Comparator<ContentRatingSystem> DISPLAY_NAME_COMPARATOR =
36             new Comparator<ContentRatingSystem>() {
37                 @Override
38                 public int compare(ContentRatingSystem s1, ContentRatingSystem s2) {
39                     String name1 = s1.getDisplayName();
40                     String name2 = s2.getDisplayName();
41                     return name1.compareTo(name2);
42                 }
43             };
44 
45     private static final String DELIMITER = "/";
46 
47     // Name of this content rating system. It should be unique in an XML file.
48     private final String mName;
49 
50     // Domain of this content rating system. It's package name now.
51     private final String mDomain;
52 
53     // Title of this content rating system. (e.g. TV-PG)
54     private final String mTitle;
55 
56     // Description of this content rating system.
57     private final String mDescription;
58 
59     // Country code of this content rating system.
60     private final List<String> mCountries;
61 
62     // Display name of this content rating system consisting of the associated country
63     // and its title. For example, "Canada (French)"
64     private final String mDisplayName;
65 
66     // Ordered list of main content ratings. UX should respect the order.
67     private final List<Rating> mRatings;
68 
69     // Ordered list of sub content ratings. UX should respect the order.
70     private final List<SubRating> mSubRatings;
71 
72     // List of orders. This describes the automatic lock/unlock relationship between ratings.
73     // For example, let say we have following order.
74     //    <order>
75     //        <rating android:name="US_TVPG_Y" />
76     //        <rating android:name="US_TVPG_Y7" />
77     //    </order>
78     // This means that locking US_TVPG_Y7 automatically locks US_TVPG_Y and
79     // unlocking US_TVPG_Y automatically unlocks US_TVPG_Y7 from the UX.
80     // An user can still unlock US_TVPG_Y while US_TVPG_Y7 is locked by manually.
81     private final List<Order> mOrders;
82 
83     private final boolean mIsCustom;
84 
getId()85     public String getId() {
86         return mDomain + DELIMITER + mName;
87     }
88 
getName()89     public String getName(){
90         return mName;
91     }
92 
getDomain()93     public String getDomain() {
94         return mDomain;
95     }
96 
getTitle()97     public String getTitle(){
98         return mTitle;
99     }
100 
getDescription()101     public String getDescription(){
102         return mDescription;
103     }
104 
getCountries()105     public List<String> getCountries(){
106         return mCountries;
107     }
108 
getRatings()109     public List<Rating> getRatings(){
110         return mRatings;
111     }
112 
getRating(String name)113     public Rating getRating(String name) {
114         for (Rating rating : mRatings) {
115             if (TextUtils.equals(rating.getName(), name)) {
116                 return rating;
117             }
118         }
119         return null;
120     }
121 
getSubRatings()122     public List<SubRating> getSubRatings(){
123         return mSubRatings;
124     }
125 
getOrders()126     public List<Order> getOrders(){
127         return mOrders;
128     }
129 
130     /**
131      * Returns the display name of the content rating system consisting of the associated country
132      * and its title. For example, "Canada (French)".
133      */
getDisplayName()134     public String getDisplayName() {
135         return mDisplayName;
136     }
137 
isCustom()138     public boolean isCustom() {
139         return mIsCustom;
140     }
141 
142     /**
143      * Returns true if the ratings is owned by this content rating system.
144      */
ownsRating(TvContentRating rating)145     public boolean ownsRating(TvContentRating rating) {
146         return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem());
147     }
148 
149     @Override
equals(Object obj)150     public boolean equals(Object obj) {
151         if (obj instanceof ContentRatingSystem) {
152             ContentRatingSystem other = (ContentRatingSystem) obj;
153             return this.mName.equals(other.mName) && this.mDomain.equals(other.mDomain);
154         }
155         return false;
156     }
157 
158     @Override
hashCode()159     public int hashCode() {
160         return 31 * mName.hashCode() + mDomain.hashCode();
161     }
162 
ContentRatingSystem( String name, String domain, String title, String description, List<String> countries, String displayName, List<Rating> ratings, List<SubRating> subRatings, List<Order> orders, boolean isCustom)163     private ContentRatingSystem(
164             String name, String domain, String title, String description, List<String> countries,
165             String displayName, List<Rating> ratings, List<SubRating> subRatings,
166             List<Order> orders, boolean isCustom) {
167         mName = name;
168         mDomain = domain;
169         mTitle = title;
170         mDescription = description;
171         mCountries = countries;
172         mDisplayName = displayName;
173         mRatings = ratings;
174         mSubRatings = subRatings;
175         mOrders = orders;
176         mIsCustom = isCustom;
177     }
178 
179     public static class Builder {
180         private final Context mContext;
181         private String mName;
182         private String mDomain;
183         private String mTitle;
184         private String mDescription;
185         private List<String> mCountries;
186         private final List<Rating.Builder> mRatingBuilders = new ArrayList<>();
187         private final List<SubRating.Builder> mSubRatingBuilders = new ArrayList<>();
188         private final List<Order.Builder> mOrderBuilders = new ArrayList<>();
189         private boolean mIsCustom;
190 
Builder(Context context)191         public Builder(Context context) {
192             mContext = context;
193         }
194 
setName(String name)195         public void setName(String name) {
196             mName = name;
197         }
198 
setDomain(String domain)199         public void setDomain(String domain) {
200             mDomain = domain;
201         }
202 
setTitle(String title)203         public void setTitle(String title) {
204             mTitle = title;
205         }
206 
setDescription(String description)207         public void setDescription(String description) {
208             mDescription = description;
209         }
210 
addCountry(String country)211         public void addCountry(String country) {
212             if (mCountries == null) {
213                 mCountries = new ArrayList<>();
214             }
215             mCountries.add(new Locale("", country).getCountry());
216         }
217 
addRatingBuilder(Rating.Builder ratingBuilder)218         public void addRatingBuilder(Rating.Builder ratingBuilder) {
219             // To provide easy access to the SubRatings in it,
220             // Rating has reference to SubRating, not Name of it.
221             // (Note that Rating/SubRating is ordered list so we cannot use Map)
222             // To do so, we need to have list of all SubRatings which might not be available
223             // at this moment. Keep builders here and build it with SubRatings later.
224             mRatingBuilders.add(ratingBuilder);
225         }
226 
addSubRatingBuilder(SubRating.Builder subRatingBuilder)227         public void addSubRatingBuilder(SubRating.Builder subRatingBuilder) {
228             // SubRatings would be built rather to keep consistency with other fields.
229             mSubRatingBuilders.add(subRatingBuilder);
230         }
231 
addOrderBuilder(Order.Builder orderBuilder)232         public void addOrderBuilder(Order.Builder orderBuilder) {
233             // To provide easy access to the Ratings in it,
234             // Order has reference to Rating, not Name of it.
235             // (Note that Rating/SubRating is ordered list so we cannot use Map)
236             // To do so, we need to have list of all Rating which might not be available
237             // at this moment. Keep builders here and build it with Ratings later.
238             mOrderBuilders.add(orderBuilder);
239         }
240 
setIsCustom(boolean isCustom)241         public void setIsCustom(boolean isCustom) {
242             mIsCustom = isCustom;
243         }
244 
build()245         public ContentRatingSystem build() {
246             if (TextUtils.isEmpty(mName)) {
247                 throw new IllegalArgumentException("Name cannot be empty");
248             }
249             if (TextUtils.isEmpty(mDomain)) {
250                 throw new IllegalArgumentException("Domain cannot be empty");
251             }
252 
253             StringBuilder sb = new StringBuilder();
254             if (mCountries != null) {
255                 if (mCountries.size() == 1) {
256                     sb.append(new Locale("", mCountries.get(0)).getDisplayCountry());
257                 } else if (mCountries.size() > 1) {
258                     Locale locale = Locale.getDefault();
259                     if (mCountries.contains(locale.getCountry())) {
260                         // Shows the country name instead of "Other countries" if the current
261                         // country is one of the countries this rating system applies to.
262                         sb.append(locale.getDisplayCountry());
263                     } else {
264                         sb.append(mContext.getString(R.string.other_countries));
265                     }
266                 }
267             }
268             if (!TextUtils.isEmpty(mTitle)) {
269                 sb.append(" (");
270                 sb.append(mTitle);
271                 sb.append(")");
272             }
273             String displayName = sb.toString();
274 
275             List<SubRating> subRatings = new ArrayList<>();
276             if (mSubRatingBuilders != null) {
277                 for (SubRating.Builder builder : mSubRatingBuilders) {
278                     subRatings.add(builder.build());
279                 }
280             }
281 
282             if (mRatingBuilders.size() <= 0) {
283                 throw new IllegalArgumentException("Rating isn't available.");
284             }
285             List<Rating> ratings = new ArrayList<>();
286             // Map string ID to object.
287             for (Rating.Builder builder : mRatingBuilders) {
288                 ratings.add(builder.build(subRatings));
289             }
290 
291             // Sanity check.
292             for (SubRating subRating : subRatings) {
293                 boolean used = false;
294                 for (Rating rating : ratings) {
295                     if (rating.getSubRatings().contains(subRating)) {
296                         used = true;
297                         break;
298                     }
299                 }
300                 if (!used) {
301                     throw new IllegalArgumentException("Subrating " + subRating.getName() +
302                         " isn't used by any rating");
303                 }
304             }
305 
306             List<Order> orders = new ArrayList<>();
307             if (mOrderBuilders != null) {
308                 for (Order.Builder builder : mOrderBuilders) {
309                     orders.add(builder.build(ratings));
310                 }
311             }
312 
313             return new ContentRatingSystem(mName, mDomain, mTitle, mDescription, mCountries,
314                     displayName, ratings, subRatings, orders, mIsCustom);
315         }
316     }
317 
318     public static class Rating {
319         private final String mName;
320         private final String mTitle;
321         private final String mDescription;
322         private final Drawable mIcon;
323         private final int mContentAgeHint;
324         private final List<SubRating> mSubRatings;
325 
getName()326         public String getName() {
327             return mName;
328         }
329 
getTitle()330         public String getTitle() {
331             return mTitle;
332         }
333 
getDescription()334         public String getDescription() {
335             return mDescription;
336         }
337 
getIcon()338         public Drawable getIcon() {
339             return mIcon;
340         }
341 
getAgeHint()342         public int getAgeHint() {
343             return mContentAgeHint;
344         }
345 
getSubRatings()346         public List<SubRating> getSubRatings() {
347             return mSubRatings;
348         }
349 
Rating(String name, String title, String description, Drawable icon, int contentAgeHint, List<SubRating> subRatings)350         private Rating(String name, String title, String description, Drawable icon,
351                 int contentAgeHint, List<SubRating> subRatings) {
352             mName = name;
353             mTitle = title;
354             mDescription = description;
355             mIcon = icon;
356             mContentAgeHint = contentAgeHint;
357             mSubRatings = subRatings;
358         }
359 
360         public static class Builder {
361             private String mName;
362             private String mTitle;
363             private String mDescription;
364             private Drawable mIcon;
365             private int mContentAgeHint = -1;
366             private final List<String> mSubRatingNames = new ArrayList<>();
367 
Builder()368             public Builder() {
369             }
370 
setName(String name)371             public void setName(String name) {
372                 mName = name;
373             }
374 
setTitle(String title)375             public void setTitle(String title) {
376                 mTitle = title;
377             }
378 
setDescription(String description)379             public void setDescription(String description) {
380                 mDescription = description;
381             }
382 
setIcon(Drawable icon)383             public void setIcon(Drawable icon) {
384                 mIcon = icon;
385             }
386 
setContentAgeHint(int contentAgeHint)387             public void setContentAgeHint(int contentAgeHint) {
388                 mContentAgeHint = contentAgeHint;
389             }
390 
addSubRatingName(String subRatingName)391             public void addSubRatingName(String subRatingName) {
392                 mSubRatingNames.add(subRatingName);
393             }
394 
build(List<SubRating> allDefinedSubRatings)395             private Rating build(List<SubRating> allDefinedSubRatings) {
396                 if (TextUtils.isEmpty(mName)) {
397                     throw new IllegalArgumentException("A rating should have non-empty name");
398                 }
399                 if (allDefinedSubRatings == null && mSubRatingNames.size() > 0) {
400                     throw new IllegalArgumentException("Invalid subrating for rating " + mName);
401                 }
402                 if (mContentAgeHint < 0) {
403                     throw new IllegalArgumentException("Rating " + mName + " should define " +
404                         "non-negative contentAgeHint");
405                 }
406 
407                 List<SubRating> subRatings = new ArrayList<>();
408                 for (String subRatingId : mSubRatingNames) {
409                     boolean found = false;
410                     for (SubRating subRating : allDefinedSubRatings) {
411                         if (subRatingId.equals(subRating.getName())) {
412                             found = true;
413                             subRatings.add(subRating);
414                             break;
415                         }
416                     }
417                     if (!found) {
418                         throw new IllegalArgumentException("Unknown subrating name " + subRatingId +
419                                 " in rating " + mName);
420                     }
421                 }
422                 return new Rating(
423                         mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings);
424             }
425         }
426     }
427 
428     public static class SubRating {
429         private final String mName;
430         private final String mTitle;
431         private final String mDescription;
432         private final Drawable mIcon;
433 
getName()434         public String getName() {
435             return mName;
436         }
437 
getTitle()438         public String getTitle() {
439             return mTitle;
440         }
441 
getDescription()442         public String getDescription() {
443             return mDescription;
444         }
445 
getIcon()446         public Drawable getIcon() {
447             return mIcon;
448         }
449 
SubRating(String name, String title, String description, Drawable icon)450         private SubRating(String name, String title, String description, Drawable icon) {
451             mName = name;
452             mTitle = title;
453             mDescription = description;
454             mIcon = icon;
455         }
456 
457         public static class Builder {
458             private String mName;
459             private String mTitle;
460             private String mDescription;
461             private Drawable mIcon;
462 
Builder()463             public Builder() {
464             }
465 
setName(String name)466             public void setName(String name) {
467                 mName = name;
468             }
469 
setTitle(String title)470             public void setTitle(String title) {
471                 mTitle = title;
472             }
473 
setDescription(String description)474             public void setDescription(String description) {
475                 mDescription = description;
476             }
477 
setIcon(Drawable icon)478             public void setIcon(Drawable icon) {
479                 mIcon = icon;
480             }
481 
build()482             private SubRating build() {
483                 if (TextUtils.isEmpty(mName)) {
484                     throw new IllegalArgumentException("A subrating should have non-empty name");
485                 }
486                 return new SubRating(mName, mTitle, mDescription, mIcon);
487             }
488         }
489     }
490 
491     public static class Order {
492         private final List<Rating> mRatingOrder;
493 
getRatingOrder()494         public List<Rating> getRatingOrder() {
495             return mRatingOrder;
496         }
497 
Order(List<Rating> ratingOrder)498         private Order(List<Rating> ratingOrder) {
499             mRatingOrder = ratingOrder;
500         }
501 
502         /**
503          * Returns index of the rating in this order.
504          * Returns -1 if this order doesn't contain the rating.
505          */
getRatingIndex(Rating rating)506         public int getRatingIndex(Rating rating) {
507             for (int i = 0; i < mRatingOrder.size(); i++) {
508                 if (mRatingOrder.get(i).getName().equals(rating.getName())) {
509                     return i;
510                 }
511             }
512             return -1;
513         }
514 
515         public static class Builder {
516             private final List<String> mRatingNames = new ArrayList<>();
517 
Builder()518             public Builder() {
519             }
520 
build(List<Rating> ratings)521             private Order build(List<Rating> ratings) {
522                 List<Rating> ratingOrder = new ArrayList<>();
523                 for (String ratingName : mRatingNames) {
524                     boolean found = false;
525                     for (Rating rating : ratings) {
526                         if (ratingName.equals(rating.getName())) {
527                             found = true;
528                             ratingOrder.add(rating);
529                             break;
530                         }
531                     }
532 
533                     if (!found) {
534                         throw new IllegalArgumentException("Unknown rating " + ratingName +
535                                 " in rating-order tag");
536                     }
537                 }
538 
539                 return new Order(ratingOrder);
540             }
541 
addRatingName(String name)542             public void addRatingName(String name) {
543                 mRatingNames.add(name);
544             }
545         }
546     }
547 }
548