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.common;
18 
19 import android.media.tv.TvContentRating;
20 import android.support.annotation.Nullable;
21 import android.support.annotation.VisibleForTesting;
22 import android.text.TextUtils;
23 import android.util.ArrayMap;
24 import android.util.Log;
25 import com.android.tv.common.memory.MemoryManageable;
26 import com.google.common.collect.ImmutableList;
27 import java.util.Collections;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.SortedSet;
31 import java.util.TreeSet;
32 
33 /** TvContentRating cache. */
34 public final class TvContentRatingCache implements MemoryManageable {
35     private static final String TAG = "TvContentRatings";
36 
37     private static final TvContentRatingCache INSTANCE = new TvContentRatingCache();
38 
getInstance()39     public static TvContentRatingCache getInstance() {
40         return INSTANCE;
41     }
42 
43     // @GuardedBy("TvContentRatingCache.this")
44     private final Map<String, ImmutableList<TvContentRating>> mRatingsMultiMap = new ArrayMap<>();
45 
46     /**
47      * Returns an array TvContentRatings from a string of comma separated set of rating strings
48      * creating each from {@link TvContentRating#unflattenFromString(String)} if needed or an empty
49      * list if the string is empty or contains no valid ratings.
50      */
getRatings( @ullable String commaSeparatedRatings)51     public synchronized ImmutableList<TvContentRating> getRatings(
52             @Nullable String commaSeparatedRatings) {
53         if (TextUtils.isEmpty(commaSeparatedRatings)) {
54             return ImmutableList.of();
55         }
56         ImmutableList<TvContentRating> tvContentRatings;
57         if (mRatingsMultiMap.containsKey(commaSeparatedRatings)) {
58             tvContentRatings = mRatingsMultiMap.get(commaSeparatedRatings);
59         } else {
60             String normalizedRatings =
61                     TextUtils.join(",", getSortedSetFromCsv(commaSeparatedRatings));
62             if (mRatingsMultiMap.containsKey(normalizedRatings)) {
63                 tvContentRatings = mRatingsMultiMap.get(normalizedRatings);
64             } else {
65                 tvContentRatings = stringToContentRatings(commaSeparatedRatings);
66                 mRatingsMultiMap.put(normalizedRatings, tvContentRatings);
67             }
68             if (!normalizedRatings.equals(commaSeparatedRatings)) {
69                 // Add an entry so the non normalized entry points to the same result;
70                 mRatingsMultiMap.put(commaSeparatedRatings, tvContentRatings);
71             }
72         }
73         return tvContentRatings;
74     }
75 
76     /** Returns a sorted array of TvContentRatings from a comma separated string of ratings. */
77     @VisibleForTesting
stringToContentRatings( @ullable String commaSeparatedRatings)78     static ImmutableList<TvContentRating> stringToContentRatings(
79             @Nullable String commaSeparatedRatings) {
80         if (TextUtils.isEmpty(commaSeparatedRatings)) {
81             return ImmutableList.of();
82         }
83         Set<String> ratingStrings = getSortedSetFromCsv(commaSeparatedRatings);
84         ImmutableList.Builder<TvContentRating> contentRatings = ImmutableList.builder();
85         for (String rating : ratingStrings) {
86             try {
87                 contentRatings.add(TvContentRating.unflattenFromString(rating));
88             } catch (IllegalArgumentException e) {
89                 Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e);
90             }
91         }
92         return contentRatings.build();
93     }
94 
getSortedSetFromCsv(String commaSeparatedRatings)95     private static Set<String> getSortedSetFromCsv(String commaSeparatedRatings) {
96         String[] ratingStrings = commaSeparatedRatings.split("\\s*,\\s*");
97         return toSortedSet(ratingStrings);
98     }
99 
toSortedSet(String[] ratingStrings)100     private static Set<String> toSortedSet(String[] ratingStrings) {
101         if (ratingStrings.length == 0) {
102             return Collections.EMPTY_SET;
103         } else if (ratingStrings.length == 1) {
104             return Collections.singleton(ratingStrings[0]);
105         } else {
106             // Using a TreeSet here is not very efficient, however it is good enough because:
107             //  - the results are cached
108             //  - in testing with multiple TISs, less than 50 entries are created
109             SortedSet<String> set = new TreeSet<>();
110             Collections.addAll(set, ratingStrings);
111             return set;
112         }
113     }
114 
115     /**
116      * Returns a string of each flattened content rating, sorted and concatenated together with a
117      * comma.
118      */
119     @Nullable
contentRatingsToString( @ullable ImmutableList<TvContentRating> contentRatings)120     public static String contentRatingsToString(
121             @Nullable ImmutableList<TvContentRating> contentRatings) {
122         if (contentRatings == null) {
123             return null;
124         }
125         SortedSet<String> ratingStrings = new TreeSet<>();
126         for (TvContentRating rating : contentRatings) {
127             ratingStrings.add(rating.flattenToString());
128         }
129         return TextUtils.join(",", ratingStrings);
130     }
131 
132     @Override
performTrimMemory(int level)133     public synchronized void performTrimMemory(int level) {
134         mRatingsMultiMap.clear();
135     }
136 
TvContentRatingCache()137     private TvContentRatingCache() {}
138 }
139