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