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