blob: b40c1133b7fae8465811a2079d0254aff66a10f5 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.parental;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.tv.TvContentRating;
import android.text.TextUtils;
import com.android.tv.R;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
public class ContentRatingSystem {
/*
* A comparator that implements the display order of a group of content rating systems.
*/
public static final Comparator<ContentRatingSystem> DISPLAY_NAME_COMPARATOR =
(ContentRatingSystem s1, ContentRatingSystem s2) -> {
String name1 = s1.getDisplayName();
String name2 = s2.getDisplayName();
return name1.compareTo(name2);
};
private static final String DELIMITER = "/";
// Name of this content rating system. It should be unique in an XML file.
private final String mName;
// Domain of this content rating system. It's package name now.
private final String mDomain;
// Title of this content rating system. (e.g. TV-PG)
private final String mTitle;
// Description of this content rating system.
private final String mDescription;
// Country code of this content rating system.
private final List<String> mCountries;
// Display name of this content rating system consisting of the associated country
// and its title. For example, "Canada (French)"
private final String mDisplayName;
// Ordered list of main content ratings. UX should respect the order.
private final List<Rating> mRatings;
// Ordered list of sub content ratings. UX should respect the order.
private final List<SubRating> mSubRatings;
// List of orders. This describes the automatic lock/unlock relationship between ratings.
// For example, let say we have following order.
// <order>
// <rating android:name="US_TVPG_Y" />
// <rating android:name="US_TVPG_Y7" />
// </order>
// This means that locking US_TVPG_Y7 automatically locks US_TVPG_Y and
// unlocking US_TVPG_Y automatically unlocks US_TVPG_Y7 from the UX.
// An user can still unlock US_TVPG_Y while US_TVPG_Y7 is locked by manually.
private final List<Order> mOrders;
private final boolean mIsCustom;
public String getId() {
return mDomain + DELIMITER + mName;
}
public String getName() {
return mName;
}
public String getDomain() {
return mDomain;
}
public String getTitle() {
return mTitle;
}
public String getDescription() {
return mDescription;
}
public List<String> getCountries() {
return mCountries;
}
public List<Rating> getRatings() {
return mRatings;
}
public Rating getRating(String name) {
for (Rating rating : mRatings) {
if (TextUtils.equals(rating.getName(), name)) {
return rating;
}
}
return null;
}
public List<SubRating> getSubRatings() {
return mSubRatings;
}
public List<Order> getOrders() {
return mOrders;
}
/**
* Returns the display name of the content rating system consisting of the associated country
* and its title. For example, "Canada (French)".
*/
public String getDisplayName() {
return mDisplayName;
}
public boolean isCustom() {
return mIsCustom;
}
/** Returns true if the ratings is owned by this content rating system. */
public boolean ownsRating(TvContentRating rating) {
return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ContentRatingSystem) {
ContentRatingSystem other = (ContentRatingSystem) obj;
return this.mName.equals(other.mName) && this.mDomain.equals(other.mDomain);
}
return false;
}
@Override
public int hashCode() {
return 31 * mName.hashCode() + mDomain.hashCode();
}
private 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) {
mName = name;
mDomain = domain;
mTitle = title;
mDescription = description;
mCountries = countries;
mDisplayName = displayName;
mRatings = ratings;
mSubRatings = subRatings;
mOrders = orders;
mIsCustom = isCustom;
}
public static class Builder {
private final Context mContext;
private String mName;
private String mDomain;
private String mTitle;
private String mDescription;
private List<String> mCountries;
private final List<Rating.Builder> mRatingBuilders = new ArrayList<>();
private final List<SubRating.Builder> mSubRatingBuilders = new ArrayList<>();
private final List<Order.Builder> mOrderBuilders = new ArrayList<>();
private boolean mIsCustom;
public Builder(Context context) {
mContext = context;
}
public void setName(String name) {
mName = name;
}
public void setDomain(String domain) {
mDomain = domain;
}
public void setTitle(String title) {
mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
public void addCountry(String country) {
if (mCountries == null) {
mCountries = new ArrayList<>();
}
mCountries.add(new Locale("", country).getCountry());
}
public void addRatingBuilder(Rating.Builder ratingBuilder) {
// To provide easy access to the SubRatings in it,
// Rating has reference to SubRating, not Name of it.
// (Note that Rating/SubRating is ordered list so we cannot use Map)
// To do so, we need to have list of all SubRatings which might not be available
// at this moment. Keep builders here and build it with SubRatings later.
mRatingBuilders.add(ratingBuilder);
}
public void addSubRatingBuilder(SubRating.Builder subRatingBuilder) {
// SubRatings would be built rather to keep consistency with other fields.
mSubRatingBuilders.add(subRatingBuilder);
}
public void addOrderBuilder(Order.Builder orderBuilder) {
// To provide easy access to the Ratings in it,
// Order has reference to Rating, not Name of it.
// (Note that Rating/SubRating is ordered list so we cannot use Map)
// To do so, we need to have list of all Rating which might not be available
// at this moment. Keep builders here and build it with Ratings later.
mOrderBuilders.add(orderBuilder);
}
public void setIsCustom(boolean isCustom) {
mIsCustom = isCustom;
}
public ContentRatingSystem build() {
if (TextUtils.isEmpty(mName)) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (TextUtils.isEmpty(mDomain)) {
throw new IllegalArgumentException("Domain cannot be empty");
}
StringBuilder sb = new StringBuilder();
if (mCountries != null) {
if (mCountries.size() == 1) {
sb.append(new Locale("", mCountries.get(0)).getDisplayCountry());
} else if (mCountries.size() > 1) {
Locale locale = Locale.getDefault();
if (mCountries.contains(locale.getCountry())) {
// Shows the country name instead of "Other countries" if the current
// country is one of the countries this rating system applies to.
sb.append(locale.getDisplayCountry());
} else {
sb.append(mContext.getString(R.string.other_countries));
}
}
}
if (!TextUtils.isEmpty(mTitle)) {
sb.append(" (");
sb.append(mTitle);
sb.append(")");
}
String displayName = sb.toString();
List<SubRating> subRatings = new ArrayList<>();
if (mSubRatingBuilders != null) {
for (SubRating.Builder builder : mSubRatingBuilders) {
subRatings.add(builder.build());
}
}
if (mRatingBuilders.size() <= 0) {
throw new IllegalArgumentException("Rating isn't available.");
}
List<Rating> ratings = new ArrayList<>();
// Map string ID to object.
for (Rating.Builder builder : mRatingBuilders) {
ratings.add(builder.build(subRatings));
}
// Soundness check.
for (SubRating subRating : subRatings) {
boolean used = false;
for (Rating rating : ratings) {
if (rating.getSubRatings().contains(subRating)) {
used = true;
break;
}
}
if (!used) {
throw new IllegalArgumentException(
"Subrating " + subRating.getName() + " isn't used by any rating");
}
}
List<Order> orders = new ArrayList<>();
if (mOrderBuilders != null) {
for (Order.Builder builder : mOrderBuilders) {
orders.add(builder.build(ratings));
}
}
return new ContentRatingSystem(
mName,
mDomain,
mTitle,
mDescription,
mCountries,
displayName,
ratings,
subRatings,
orders,
mIsCustom);
}
}
public static class Rating {
private final String mName;
private final String mTitle;
private final String mDescription;
private final Drawable mIcon;
private final int mContentAgeHint;
private final List<SubRating> mSubRatings;
public String getName() {
return mName;
}
public String getTitle() {
return mTitle;
}
public String getDescription() {
return mDescription;
}
public Drawable getIcon() {
return mIcon;
}
public int getAgeHint() {
return mContentAgeHint;
}
public List<SubRating> getSubRatings() {
return mSubRatings;
}
private Rating(
String name,
String title,
String description,
Drawable icon,
int contentAgeHint,
List<SubRating> subRatings) {
mName = name;
mTitle = title;
mDescription = description;
mIcon = icon;
mContentAgeHint = contentAgeHint;
mSubRatings = subRatings;
}
public static class Builder {
private String mName;
private String mTitle;
private String mDescription;
private Drawable mIcon;
private int mContentAgeHint = -1;
private final List<String> mSubRatingNames = new ArrayList<>();
public Builder() {}
public void setName(String name) {
mName = name;
}
public void setTitle(String title) {
mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
public void setContentAgeHint(int contentAgeHint) {
mContentAgeHint = contentAgeHint;
}
public void addSubRatingName(String subRatingName) {
mSubRatingNames.add(subRatingName);
}
private Rating build(List<SubRating> allDefinedSubRatings) {
if (TextUtils.isEmpty(mName)) {
throw new IllegalArgumentException("A rating should have non-empty name");
}
if (allDefinedSubRatings == null && mSubRatingNames.size() > 0) {
throw new IllegalArgumentException("Invalid subrating for rating " + mName);
}
if (mContentAgeHint < 0) {
throw new IllegalArgumentException(
"Rating " + mName + " should define " + "non-negative contentAgeHint");
}
List<SubRating> subRatings = new ArrayList<>();
for (String subRatingId : mSubRatingNames) {
boolean found = false;
for (SubRating subRating : allDefinedSubRatings) {
if (subRatingId.equals(subRating.getName())) {
found = true;
subRatings.add(subRating);
break;
}
}
if (!found) {
throw new IllegalArgumentException(
"Unknown subrating name " + subRatingId + " in rating " + mName);
}
}
return new Rating(mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings);
}
}
}
public static class SubRating {
private final String mName;
private final String mTitle;
private final String mDescription;
private final Drawable mIcon;
public String getName() {
return mName;
}
public String getTitle() {
return mTitle;
}
public String getDescription() {
return mDescription;
}
public Drawable getIcon() {
return mIcon;
}
private SubRating(String name, String title, String description, Drawable icon) {
mName = name;
mTitle = title;
mDescription = description;
mIcon = icon;
}
public static class Builder {
private String mName;
private String mTitle;
private String mDescription;
private Drawable mIcon;
public Builder() {}
public void setName(String name) {
mName = name;
}
public void setTitle(String title) {
mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
private SubRating build() {
if (TextUtils.isEmpty(mName)) {
throw new IllegalArgumentException("A subrating should have non-empty name");
}
return new SubRating(mName, mTitle, mDescription, mIcon);
}
}
}
public static class Order {
private final List<Rating> mRatingOrder;
public List<Rating> getRatingOrder() {
return mRatingOrder;
}
private Order(List<Rating> ratingOrder) {
mRatingOrder = ratingOrder;
}
/**
* Returns index of the rating in this order. Returns -1 if this order doesn't contain the
* rating.
*/
public int getRatingIndex(Rating rating) {
for (int i = 0; i < mRatingOrder.size(); i++) {
if (mRatingOrder.get(i).getName().equals(rating.getName())) {
return i;
}
}
return -1;
}
public static class Builder {
private final List<String> mRatingNames = new ArrayList<>();
public Builder() {}
private Order build(List<Rating> ratings) {
List<Rating> ratingOrder = new ArrayList<>();
for (String ratingName : mRatingNames) {
boolean found = false;
for (Rating rating : ratings) {
if (ratingName.equals(rating.getName())) {
found = true;
ratingOrder.add(rating);
break;
}
}
if (!found) {
throw new IllegalArgumentException(
"Unknown rating " + ratingName + " in rating-order tag");
}
}
return new Order(ratingOrder);
}
public void addRatingName(String name) {
mRatingNames.add(name);
}
}
}
}