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 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 
    122     public List<SubRating> getSubRatings(){
    123         return mSubRatings;
    124     }
    125 
    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      */
    134     public String getDisplayName() {
    135         return mDisplayName;
    136     }
    137 
    138     public boolean isCustom() {
    139         return mIsCustom;
    140     }
    141 
    142     /**
    143      * Returns true if the ratings is owned by this content rating system.
    144      */
    145     public boolean ownsRating(TvContentRating rating) {
    146         return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem());
    147     }
    148 
    149     @Override
    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
    159     public int hashCode() {
    160         return 31 * mName.hashCode() + mDomain.hashCode();
    161     }
    162 
    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 
    191         public Builder(Context context) {
    192             mContext = context;
    193         }
    194 
    195         public void setName(String name) {
    196             mName = name;
    197         }
    198 
    199         public void setDomain(String domain) {
    200             mDomain = domain;
    201         }
    202 
    203         public void setTitle(String title) {
    204             mTitle = title;
    205         }
    206 
    207         public void setDescription(String description) {
    208             mDescription = description;
    209         }
    210 
    211         public void addCountry(String country) {
    212             if (mCountries == null) {
    213                 mCountries = new ArrayList<>();
    214             }
    215             mCountries.add(new Locale("", country).getCountry());
    216         }
    217 
    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 
    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 
    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 
    241         public void setIsCustom(boolean isCustom) {
    242             mIsCustom = isCustom;
    243         }
    244 
    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 
    326         public String getName() {
    327             return mName;
    328         }
    329 
    330         public String getTitle() {
    331             return mTitle;
    332         }
    333 
    334         public String getDescription() {
    335             return mDescription;
    336         }
    337 
    338         public Drawable getIcon() {
    339             return mIcon;
    340         }
    341 
    342         public int getAgeHint() {
    343             return mContentAgeHint;
    344         }
    345 
    346         public List<SubRating> getSubRatings() {
    347             return mSubRatings;
    348         }
    349 
    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 
    368             public Builder() {
    369             }
    370 
    371             public void setName(String name) {
    372                 mName = name;
    373             }
    374 
    375             public void setTitle(String title) {
    376                 mTitle = title;
    377             }
    378 
    379             public void setDescription(String description) {
    380                 mDescription = description;
    381             }
    382 
    383             public void setIcon(Drawable icon) {
    384                 mIcon = icon;
    385             }
    386 
    387             public void setContentAgeHint(int contentAgeHint) {
    388                 mContentAgeHint = contentAgeHint;
    389             }
    390 
    391             public void addSubRatingName(String subRatingName) {
    392                 mSubRatingNames.add(subRatingName);
    393             }
    394 
    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 
    434         public String getName() {
    435             return mName;
    436         }
    437 
    438         public String getTitle() {
    439             return mTitle;
    440         }
    441 
    442         public String getDescription() {
    443             return mDescription;
    444         }
    445 
    446         public Drawable getIcon() {
    447             return mIcon;
    448         }
    449 
    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 
    463             public Builder() {
    464             }
    465 
    466             public void setName(String name) {
    467                 mName = name;
    468             }
    469 
    470             public void setTitle(String title) {
    471                 mTitle = title;
    472             }
    473 
    474             public void setDescription(String description) {
    475                 mDescription = description;
    476             }
    477 
    478             public void setIcon(Drawable icon) {
    479                 mIcon = icon;
    480             }
    481 
    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 
    494         public List<Rating> getRatingOrder() {
    495             return mRatingOrder;
    496         }
    497 
    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          */
    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 
    518             public Builder() {
    519             }
    520 
    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 
    542             public void addRatingName(String name) {
    543                 mRatingNames.add(name);
    544             }
    545         }
    546     }
    547 }
    548