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