diff options
Diffstat (limited to 'src/com/android/tv/parental/ContentRatingsParser.java')
-rw-r--r-- | src/com/android/tv/parental/ContentRatingsParser.java | 453 |
1 files changed, 278 insertions, 175 deletions
diff --git a/src/com/android/tv/parental/ContentRatingsParser.java b/src/com/android/tv/parental/ContentRatingsParser.java index b999f938..d9f62473 100644 --- a/src/com/android/tv/parental/ContentRatingsParser.java +++ b/src/com/android/tv/parental/ContentRatingsParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -16,318 +16,421 @@ package com.android.tv.parental; -import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; -import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.media.tv.TvContentRatingSystemInfo; import android.net.Uri; import android.util.Log; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - +import com.android.tv.parental.ContentRatingSystem.Order; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; -import com.android.tv.parental.ContentRatingSystem.Order; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + public class ContentRatingsParser { - private final static String TAG = "ContentRatingsParser"; - - private final static String TAG_RATING_SYSTEM_DEFINITIONS = "rating-system-definitions"; - private final static String TAG_RATING_SYSTEM_DEFINITION = "rating-system-definition"; - private final static String TAG_SUB_RATING_DEFINITION = "sub-rating-definition"; - private final static String TAG_RATING_DEFINITION = "rating-definition"; - private final static String TAG_SUB_RATING = "sub-rating"; - private final static String TAG_ORDER = "order"; - private final static String TAG_RATING = "rating"; - - private final static String ATTR_ID = "id"; - private final static String ATTR_DISPLAY_NAME = "displayName"; - private final static String ATTR_COUNTRY = "country"; - private final static String ATTR_ICON = "icon"; - private final static String ATTR_DESCRIPTION = "description"; - private final static String ATTR_AGE_HINT = "ageHint"; - - private ContentRatingsParser() { - // Prevent instantiation. + private static final String TAG = "ContentRatingsParser"; + private static final boolean DEBUG = false; + + public static final String DOMAIN_SYSTEM_RATINGS = "com.android.tv"; + + private static final String TAG_RATING_SYSTEM_DEFINITIONS = "rating-system-definitions"; + private static final String TAG_RATING_SYSTEM_DEFINITION = "rating-system-definition"; + private static final String TAG_SUB_RATING_DEFINITION = "sub-rating-definition"; + private static final String TAG_RATING_DEFINITION = "rating-definition"; + private static final String TAG_SUB_RATING = "sub-rating"; + private static final String TAG_RATING = "rating"; + private static final String TAG_RATING_ORDER = "rating-order"; + + private static final String ATTR_VERSION_CODE = "versionCode"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_TITLE = "title"; + private static final String ATTR_COUNTRY = "country"; + private static final String ATTR_ICON = "icon"; + private static final String ATTR_DESCRIPTION = "description"; + private static final String ATTR_CONTENT_AGE_HINT = "contentAgeHint"; + private static final String VERSION_CODE = "1"; + + private final Context mContext; + private Resources mResources; + private String mXmlVersionCode; + + public ContentRatingsParser(Context context) { + mContext = context; } - public static List<ContentRatingSystem> parse(Context context, Uri uri) { + public List<ContentRatingSystem> parse(TvContentRatingSystemInfo info) { List<ContentRatingSystem> ratingSystems = null; - XmlResourceParser parser = null; + Uri uri = info.getXmlUri(); + if (DEBUG) Log.d(TAG, "Parsing rating system for " + uri); try { - if (!uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) { - throw new IllegalArgumentException("Invalid URI scheme " + uri); - } String packageName = uri.getAuthority(); int resId = (int) ContentUris.parseId(uri); - parser = context.getPackageManager().getXml(packageName, resId, null); - if (parser == null) { - throw new IllegalArgumentException("Cannot get XML with URI " + uri); + try (XmlResourceParser parser = mContext.getPackageManager() + .getXml(packageName, resId, null)) { + if (parser == null) { + throw new IllegalArgumentException("Cannot get XML with URI " + uri); + } + ratingSystems = parse(parser, packageName, !info.isSystemDefined()); } - ratingSystems = parse(parser, packageName); } catch (Exception e) { // Catching all exceptions and print which URI is malformed XML with description // and stack trace here. - // TODO: We may want to print message to stdout. see b/16803331 + // TODO: We may want to print message to stdout. Log.w(TAG, "Error parsing XML " + uri, e); - } finally { - if (parser != null) { - parser.close(); - } } - return ratingSystems; } - private static void assertEquals(int a, int b, String msg) throws XmlPullParserException { - if (a != b) { - throw new XmlPullParserException(msg); + private List<ContentRatingSystem> parse(XmlResourceParser parser, String domain, + boolean isCustom) + throws XmlPullParserException, IOException { + try { + mResources = mContext.getPackageManager().getResourcesForApplication(domain); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to get resources for " + domain, e); + mResources = mContext.getResources(); } - } - - private static void assertEquals(String a, String b, String msg) throws XmlPullParserException { - if (!b.equals(a)) { - throw new XmlPullParserException(msg); + // TODO: find another way to replace the domain the content rating systems defined in TV. + // Live TV app provides public content rating systems. Therefore, the domain of + // the content rating systems defined in TV app should be com.android.tv instead of + // this app's package name. + if (domain.equals(mContext.getPackageName())) { + domain = DOMAIN_SYSTEM_RATINGS; } - } - private static List<ContentRatingSystem> parse(XmlResourceParser parser, String domain) - throws XmlPullParserException, IOException { // Consume all START_DOCUMENT which can appear more than once. - while (parser.next() == XmlPullParser.START_DOCUMENT); + while (parser.next() == XmlPullParser.START_DOCUMENT) {} int eventType = parser.getEventType(); assertEquals(eventType, XmlPullParser.START_TAG, "Malformed XML: Not a valid XML file"); assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS, "Malformed XML: Should start with tag " + TAG_RATING_SYSTEM_DEFINITIONS); - List<ContentRatingSystem> ratingSystems = new ArrayList<ContentRatingSystem>(); - while (true) { - eventType = parser.nextTag(); + boolean hasVersionAttr = false; + for (int i = 0; i < parser.getAttributeCount(); i++) { + String attr = parser.getAttributeName(i); + if (ATTR_VERSION_CODE.equals(attr)) { + hasVersionAttr = true; + mXmlVersionCode = parser.getAttributeValue(i); + } + } + if (!hasVersionAttr) { + throw new XmlPullParserException("Malformed XML: Should contains a version attribute" + + " in " + TAG_RATING_SYSTEM_DEFINITIONS); + } - switch (eventType) { + List<ContentRatingSystem> ratingSystems = new ArrayList<>(); + while (parser.next() != XmlPullParser.END_DOCUMENT) { + switch (parser.getEventType()) { case XmlPullParser.START_TAG: - assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITION, - "Malformed XML: Should contains " + - TAG_RATING_SYSTEM_DEFINITION); - ratingSystems.add(parseRatingSystem(parser, domain)); + if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) { + ratingSystems.add(parseRatingSystemDefinition(parser, domain, isCustom)); + } else { + checkVersion("Malformed XML: Should contains " + + TAG_RATING_SYSTEM_DEFINITION); + } break; case XmlPullParser.END_TAG: - assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS, - "Malformed XML: Should end with tag " + - TAG_RATING_SYSTEM_DEFINITIONS); - eventType = parser.next(); - assertEquals(eventType, XmlPullParser.END_DOCUMENT, - "Malformed XML: Should end with tag " + - TAG_RATING_SYSTEM_DEFINITIONS); - return ratingSystems; - default: - throw new XmlPullParserException("Malformed XML: Error in " + - TAG_RATING_SYSTEM_DEFINITIONS); + if (TAG_RATING_SYSTEM_DEFINITIONS.equals(parser.getName())) { + eventType = parser.next(); + assertEquals(eventType, XmlPullParser.END_DOCUMENT, + "Malformed XML: Should end with tag " + + TAG_RATING_SYSTEM_DEFINITIONS); + return ratingSystems; + } else { + checkVersion("Malformed XML: Should end with tag " + + TAG_RATING_SYSTEM_DEFINITIONS); + } } } + throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITIONS + + " section is incomplete or section ending tag is missing"); } - private static ContentRatingSystem parseRatingSystem(XmlResourceParser parser, String domain) - throws XmlPullParserException, IOException { - ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder(); + private static void assertEquals(int a, int b, String msg) throws XmlPullParserException { + if (a != b) { + throw new XmlPullParserException(msg); + } + } + + private static void assertEquals(String a, String b, String msg) throws XmlPullParserException { + if (!b.equals(a)) { + throw new XmlPullParserException(msg); + } + } + + private void checkVersion(String msg) throws XmlPullParserException { + if (!VERSION_CODE.equals(mXmlVersionCode)) { + throw new XmlPullParserException(msg); + } + } + + private ContentRatingSystem parseRatingSystemDefinition(XmlResourceParser parser, String domain, + boolean isCustom) throws XmlPullParserException, IOException { + ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder(mContext); builder.setDomain(domain); for (int i = 0; i < parser.getAttributeCount(); i++) { String attr = parser.getAttributeName(i); switch (attr) { - case ATTR_ID: - builder.setId(parser.getAttributeValue(i)); + case ATTR_NAME: + builder.setName(parser.getAttributeValue(i)); break; case ATTR_COUNTRY: - builder.setCountry(parser.getAttributeValue(i)); + for (String country : parser.getAttributeValue(i).split("\\s*,\\s*")) { + builder.addCountry(country); + } break; - case ATTR_DISPLAY_NAME: - builder.setDisplayName(parser.getAttributeValue(i)); + case ATTR_TITLE: + builder.setTitle(getTitle(parser, i)); break; case ATTR_DESCRIPTION: - builder.setDisplayName(parser.getAttributeValue(i)); + builder.setDescription( + mResources.getString(parser.getAttributeResourceValue(i, 0))); break; default: - throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr + - " in " + TAG_RATING_SYSTEM_DEFINITIONS); + checkVersion("Malformed XML: Unknown attribute " + attr + " in " + + TAG_RATING_SYSTEM_DEFINITION); } } - while (true) { - int eventType = parser.nextTag(); - switch (eventType) { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + switch (parser.getEventType()) { case XmlPullParser.START_TAG: String tag = parser.getName(); switch (tag) { case TAG_RATING_DEFINITION: - builder.addRatingBuilder(parseRating(parser)); + builder.addRatingBuilder(parseRatingDefinition(parser)); break; case TAG_SUB_RATING_DEFINITION: - builder.addSubRatingBuilder(parseSubRating(parser)); + builder.addSubRatingBuilder(parseSubRatingDefinition(parser)); break; - case TAG_ORDER: + case TAG_RATING_ORDER: builder.addOrderBuilder(parseOrder(parser)); break; default: - throw new XmlPullParserException("Malformed XML: Unknown tag " + tag + - " in " + TAG_RATING_SYSTEM_DEFINITION); + checkVersion("Malformed XML: Unknown tag " + tag + " in " + + TAG_RATING_SYSTEM_DEFINITION); } break; case XmlPullParser.END_TAG: - assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITION, - "Malformed XML: Tag mismatch for " + TAG_RATING_SYSTEM_DEFINITION); - return builder.build(); - default: - throw new XmlPullParserException("Malformed XML: Tag is expected in " + - TAG_RATING_SYSTEM_DEFINITION); + if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) { + builder.setIsCustom(isCustom); + return builder.build(); + } else { + checkVersion("Malformed XML: Tag mismatch for " + + TAG_RATING_SYSTEM_DEFINITION); + } } } + throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITION + + " section is incomplete or section ending tag is missing"); } - private static Rating.Builder parseRating(XmlResourceParser parser) + private Rating.Builder parseRatingDefinition(XmlResourceParser parser) throws XmlPullParserException, IOException { Rating.Builder builder = new Rating.Builder(); for (int i = 0; i < parser.getAttributeCount(); i++) { String attr = parser.getAttributeName(i); switch (attr) { - case ATTR_ID: - builder.setId(parser.getAttributeValue(i)); + case ATTR_NAME: + builder.setName(parser.getAttributeValue(i)); break; - case ATTR_DISPLAY_NAME: - builder.setDisplayName(parser.getAttributeValue(i)); + case ATTR_TITLE: + builder.setTitle(getTitle(parser, i)); break; case ATTR_DESCRIPTION: - builder.setDisplayName(parser.getAttributeValue(i)); + builder.setDescription( + mResources.getString(parser.getAttributeResourceValue(i, 0))); break; case ATTR_ICON: - builder.setIconUri(Uri.parse(parser.getAttributeValue(i))); + builder.setIcon( + mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null)); break; - case ATTR_AGE_HINT: - int ageHint = -1; + case ATTR_CONTENT_AGE_HINT: + int contentAgeHint = -1; try { - ageHint = Integer.parseInt(parser.getAttributeValue(i)); - } catch (NumberFormatException e) { + contentAgeHint = Integer.parseInt(parser.getAttributeValue(i)); + } catch (NumberFormatException ignored) { } - if (ageHint < 0) { - throw new XmlPullParserException("Malformed XML: " + ATTR_AGE_HINT + + if (contentAgeHint < 0) { + throw new XmlPullParserException("Malformed XML: " + ATTR_CONTENT_AGE_HINT + " should be a non-negative number"); } - builder.setAgeHint(ageHint); + builder.setContentAgeHint(contentAgeHint); break; default: - throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr + - " in " + TAG_RATING_DEFINITION); + checkVersion("Malformed XML: Unknown attribute " + attr + " in " + + TAG_RATING_DEFINITION); } } - while (true) { - int eventType = parser.nextTag(); - switch (eventType) { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + switch (parser.getEventType()) { case XmlPullParser.START_TAG: - assertEquals(parser.getName(), TAG_SUB_RATING, - "Malformed XML: Only " + TAG_SUB_RATING + " is allowed in " + - TAG_RATING_DEFINITION); - if (parser.getAttributeCount() != 1 || - !ATTR_ID.equals(parser.getAttributeName(0))) { - throw new XmlPullParserException("Malformed XML: " + TAG_SUB_RATING + - " should only contain " + ATTR_ID); - } - builder.addSubRatingId(parser.getAttributeValue(0)); - eventType = parser.nextTag(); - if (eventType != XmlPullParser.END_TAG || - !TAG_SUB_RATING.equals(parser.getName())) { - throw new XmlPullParserException("Malformed XML: " + TAG_SUB_RATING + - " has child"); + if (TAG_SUB_RATING.equals(parser.getName())) { + builder = parseSubRating(parser, builder); + } else { + checkVersion(("Malformed XML: Only " + TAG_SUB_RATING + " is allowed in " + + TAG_RATING_DEFINITION)); } break; case XmlPullParser.END_TAG: - assertEquals(parser.getName(), TAG_RATING_DEFINITION, - "Malformed XML: Tag mismatch for " + TAG_RATING_DEFINITION); - return builder; - - default: - throw new XmlPullParserException("Malformed XML: Error in " + - TAG_RATING_DEFINITION); + if (TAG_RATING_DEFINITION.equals(parser.getName())) { + return builder; + } else { + checkVersion("Malformed XML: Tag mismatch for " + TAG_RATING_DEFINITION); + } } } + throw new XmlPullParserException(TAG_RATING_DEFINITION + + " section is incomplete or section ending tag is missing"); } - private static SubRating.Builder parseSubRating(XmlResourceParser parser) + private SubRating.Builder parseSubRatingDefinition(XmlResourceParser parser) throws XmlPullParserException, IOException { SubRating.Builder builder = new SubRating.Builder(); for (int i = 0; i < parser.getAttributeCount(); i++) { String attr = parser.getAttributeName(i); switch (attr) { - case ATTR_ID: - builder.setId(parser.getAttributeValue(i)); + case ATTR_NAME: + builder.setName(parser.getAttributeValue(i)); break; - case ATTR_DISPLAY_NAME: - builder.setDisplayName(parser.getAttributeValue(i)); + case ATTR_TITLE: + builder.setTitle(getTitle(parser, i)); break; case ATTR_DESCRIPTION: - builder.setDisplayName(parser.getAttributeValue(i)); + builder.setDescription( + mResources.getString(parser.getAttributeResourceValue(i, 0))); break; case ATTR_ICON: - builder.setIconUri(Uri.parse(parser.getAttributeValue(i))); + builder.setIcon( + mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null)); break; default: - throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr + - " in " + TAG_SUB_RATING_DEFINITION); + checkVersion("Malformed XML: Unknown attribute " + attr + " in " + + TAG_SUB_RATING_DEFINITION); } } - assertEquals(parser.nextTag(), XmlPullParser.END_TAG, - "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " has child"); - assertEquals(parser.getName(), TAG_SUB_RATING_DEFINITION, - "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " isn't closed"); - - return builder; + while (parser.next() != XmlPullParser.END_DOCUMENT) { + switch (parser.getEventType()) { + case XmlPullParser.END_TAG: + if (TAG_SUB_RATING_DEFINITION.equals(parser.getName())) { + return builder; + } else { + checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION + + " isn't closed"); + } + break; + default: + checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION + " has child"); + } + } + throw new XmlPullParserException(TAG_SUB_RATING_DEFINITION + + " section is incomplete or section ending tag is missing"); } - private static Order.Builder parseOrder(XmlResourceParser parser) + private Order.Builder parseOrder(XmlResourceParser parser) throws XmlPullParserException, IOException { Order.Builder builder = new Order.Builder(); assertEquals(parser.getAttributeCount(), 0, - "Malformed XML: Attribute isn't allowed in " + TAG_ORDER); + "Malformed XML: Attribute isn't allowed in " + TAG_RATING_ORDER); - while (true) { - int eventType = parser.nextTag(); - switch (eventType) { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + switch (parser.getEventType()) { case XmlPullParser.START_TAG: - assertEquals(parser.getName(), TAG_RATING, - "Malformed XML: Only " + TAG_RATING + " is allowed in " + - TAG_ORDER); - if (parser.getAttributeCount() != 1 || - !ATTR_ID.equals(parser.getAttributeName(0))) { - throw new XmlPullParserException("Malformed XML: " + TAG_ORDER + - " should only contain " + ATTR_ID); - } - builder.addRatingId(parser.getAttributeValue(0)); - eventType = parser.nextTag(); - if (eventType != XmlPullParser.END_TAG || - !TAG_RATING.equals(parser.getName())) { - throw new XmlPullParserException("Malformed XML: " + TAG_RATING + - " has child"); + if (TAG_RATING.equals(parser.getName())) { + builder = parseRating(parser, builder); + } else { + checkVersion("Malformed XML: Only " + TAG_RATING + " is allowed in " + + TAG_RATING_ORDER); } break; case XmlPullParser.END_TAG: - assertEquals(parser.getName(), TAG_ORDER, - "Malformed XML: Tag mismatch for " + TAG_ORDER); + assertEquals(parser.getName(), TAG_RATING_ORDER, + "Malformed XML: Tag mismatch for " + TAG_RATING_ORDER); return builder; + } + } + throw new XmlPullParserException(TAG_RATING_ORDER + + " section is incomplete or section ending tag is missing"); + } + + private Order.Builder parseRating(XmlResourceParser parser, Order.Builder builder) + throws XmlPullParserException, IOException { + for (int i = 0; i < parser.getAttributeCount(); i++) { + String attr = parser.getAttributeName(i); + switch (attr) { + case ATTR_NAME: + builder.addRatingName(parser.getAttributeValue(i)); + break; default: - throw new XmlPullParserException("Malformed XML: Error in " + TAG_ORDER); + checkVersion("Malformed XML: " + TAG_RATING_ORDER + " should only contain " + + ATTR_NAME); } } + + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.END_TAG) { + if (TAG_RATING.equals(parser.getName())) { + return builder; + } else { + checkVersion("Malformed XML: " + TAG_RATING + " has child"); + } + } + } + throw new XmlPullParserException(TAG_RATING + + " section is incomplete or section ending tag is missing"); + } + + private Rating.Builder parseSubRating(XmlResourceParser parser, Rating.Builder builder) + throws XmlPullParserException, IOException { + for (int i = 0; i < parser.getAttributeCount(); i++) { + String attr = parser.getAttributeName(i); + switch (attr) { + case ATTR_NAME: + builder.addSubRatingName(parser.getAttributeValue(i)); + break; + default: + checkVersion("Malformed XML: " + TAG_SUB_RATING + " should only contain " + + ATTR_NAME); + } + } + + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.END_TAG) { + if (TAG_SUB_RATING.equals(parser.getName())) { + return builder; + } else { + checkVersion("Malformed XML: " + TAG_SUB_RATING + " has child"); + } + } + } + throw new XmlPullParserException(TAG_SUB_RATING + + " section is incomplete or section ending tag is missing"); + } + + // Title might be a resource id or a string value. Try loading as an id first, then use the + // string if that fails. + private String getTitle(XmlResourceParser parser, int index) { + int titleResId = parser.getAttributeResourceValue(index, 0); + if (titleResId != 0) { + return mResources.getString(titleResId); + } + return parser.getAttributeValue(index); } } |