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 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 
     83     public String getId() {
     84         return mDomain + DELIMITER + mName;
     85     }
     86 
     87     public String getName() {
     88         return mName;
     89     }
     90 
     91     public String getDomain() {
     92         return mDomain;
     93     }
     94 
     95     public String getTitle() {
     96         return mTitle;
     97     }
     98 
     99     public String getDescription() {
    100         return mDescription;
    101     }
    102 
    103     public List<String> getCountries() {
    104         return mCountries;
    105     }
    106 
    107     public List<Rating> getRatings() {
    108         return mRatings;
    109     }
    110 
    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 
    120     public List<SubRating> getSubRatings() {
    121         return mSubRatings;
    122     }
    123 
    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      */
    132     public String getDisplayName() {
    133         return mDisplayName;
    134     }
    135 
    136     public boolean isCustom() {
    137         return mIsCustom;
    138     }
    139 
    140     /** Returns true if the ratings is owned by this content rating system. */
    141     public boolean ownsRating(TvContentRating rating) {
    142         return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem());
    143     }
    144 
    145     @Override
    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
    155     public int hashCode() {
    156         return 31 * mName.hashCode() + mDomain.hashCode();
    157     }
    158 
    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 
    194         public Builder(Context context) {
    195             mContext = context;
    196         }
    197 
    198         public void setName(String name) {
    199             mName = name;
    200         }
    201 
    202         public void setDomain(String domain) {
    203             mDomain = domain;
    204         }
    205 
    206         public void setTitle(String title) {
    207             mTitle = title;
    208         }
    209 
    210         public void setDescription(String description) {
    211             mDescription = description;
    212         }
    213 
    214         public void addCountry(String country) {
    215             if (mCountries == null) {
    216                 mCountries = new ArrayList<>();
    217             }
    218             mCountries.add(new Locale("", country).getCountry());
    219         }
    220 
    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 
    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 
    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 
    244         public void setIsCustom(boolean isCustom) {
    245             mIsCustom = isCustom;
    246         }
    247 
    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 
    338         public String getName() {
    339             return mName;
    340         }
    341 
    342         public String getTitle() {
    343             return mTitle;
    344         }
    345 
    346         public String getDescription() {
    347             return mDescription;
    348         }
    349 
    350         public Drawable getIcon() {
    351             return mIcon;
    352         }
    353 
    354         public int getAgeHint() {
    355             return mContentAgeHint;
    356         }
    357 
    358         public List<SubRating> getSubRatings() {
    359             return mSubRatings;
    360         }
    361 
    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 
    385             public Builder() {}
    386 
    387             public void setName(String name) {
    388                 mName = name;
    389             }
    390 
    391             public void setTitle(String title) {
    392                 mTitle = title;
    393             }
    394 
    395             public void setDescription(String description) {
    396                 mDescription = description;
    397             }
    398 
    399             public void setIcon(Drawable icon) {
    400                 mIcon = icon;
    401             }
    402 
    403             public void setContentAgeHint(int contentAgeHint) {
    404                 mContentAgeHint = contentAgeHint;
    405             }
    406 
    407             public void addSubRatingName(String subRatingName) {
    408                 mSubRatingNames.add(subRatingName);
    409             }
    410 
    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 
    449         public String getName() {
    450             return mName;
    451         }
    452 
    453         public String getTitle() {
    454             return mTitle;
    455         }
    456 
    457         public String getDescription() {
    458             return mDescription;
    459         }
    460 
    461         public Drawable getIcon() {
    462             return mIcon;
    463         }
    464 
    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 
    478             public Builder() {}
    479 
    480             public void setName(String name) {
    481                 mName = name;
    482             }
    483 
    484             public void setTitle(String title) {
    485                 mTitle = title;
    486             }
    487 
    488             public void setDescription(String description) {
    489                 mDescription = description;
    490             }
    491 
    492             public void setIcon(Drawable icon) {
    493                 mIcon = icon;
    494             }
    495 
    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 
    508         public List<Rating> getRatingOrder() {
    509             return mRatingOrder;
    510         }
    511 
    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          */
    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 
    532             public Builder() {}
    533 
    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 
    555             public void addRatingName(String name) {
    556                 mRatingNames.add(name);
    557             }
    558         }
    559     }
    560 }
    561