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