• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util;
18 
19 import android.content.Context;
20 import android.content.pm.ApplicationInfo;
21 import android.media.tv.TvInputInfo;
22 import android.media.tv.TvInputManager;
23 import android.media.tv.TvInputManager.TvInputCallback;
24 import android.os.Handler;
25 import android.support.annotation.VisibleForTesting;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.tv.Features;
30 import com.android.tv.common.SoftPreconditions;
31 import com.android.tv.parental.ContentRatingsManager;
32 import com.android.tv.parental.ParentalControlSettings;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 
42 public class TvInputManagerHelper {
43     private static final String TAG = "TvInputManagerHelper";
44     private static final boolean DEBUG = false;
45     private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
46     };
47 
48     private final Context mContext;
49     private final TvInputManager mTvInputManager;
50     private final Map<String, Integer> mInputStateMap = new HashMap<>();
51     private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
52     private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
53     private final TvInputCallback mInternalCallback = new TvInputCallback() {
54         @Override
55         public void onInputStateChanged(String inputId, int state) {
56             if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
57             if (isInBlackList(inputId)) {
58                 return;
59             }
60             mInputStateMap.put(inputId, state);
61             for (TvInputCallback callback : mCallbacks) {
62                 callback.onInputStateChanged(inputId, state);
63             }
64         }
65 
66         @Override
67         public void onInputAdded(String inputId) {
68             if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
69             if (isInBlackList(inputId)) {
70                 return;
71             }
72             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
73             if (info != null) {
74                 mInputMap.put(inputId, info);
75                 mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
76                 mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
77             }
78             mContentRatingsManager.update();
79             for (TvInputCallback callback : mCallbacks) {
80                 callback.onInputAdded(inputId);
81             }
82         }
83 
84         @Override
85         public void onInputRemoved(String inputId) {
86             if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
87             mInputMap.remove(inputId);
88             mInputStateMap.remove(inputId);
89             mInputIdToPartnerInputMap.remove(inputId);
90             mContentRatingsManager.update();
91             for (TvInputCallback callback : mCallbacks) {
92                 callback.onInputRemoved(inputId);
93             }
94             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
95                     inputId));
96         }
97 
98         @Override
99         public void onInputUpdated(String inputId) {
100             if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
101             if (isInBlackList(inputId)) {
102                 return;
103             }
104             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
105             mInputMap.put(inputId, info);
106             for (TvInputCallback callback : mCallbacks) {
107                 callback.onInputUpdated(inputId);
108             }
109             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
110                     inputId));
111         }
112 
113         @Override
114         public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
115             if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
116             mInputMap.put(inputInfo.getId(), inputInfo);
117             for (TvInputCallback callback : mCallbacks) {
118                 callback.onTvInputInfoUpdated(inputInfo);
119             }
120             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
121                     inputInfo.getId()));
122         }
123     };
124 
125     private final Handler mHandler = new Handler();
126     private boolean mStarted;
127     private final HashSet<TvInputCallback> mCallbacks = new HashSet<>();
128     private final ContentRatingsManager mContentRatingsManager;
129     private final ParentalControlSettings mParentalControlSettings;
130     private final Comparator<TvInputInfo> mTvInputInfoComparator;
131 
TvInputManagerHelper(Context context)132     public TvInputManagerHelper(Context context) {
133         mContext = context.getApplicationContext();
134         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
135         mContentRatingsManager = new ContentRatingsManager(context);
136         mParentalControlSettings = new ParentalControlSettings(context);
137         mTvInputInfoComparator = new TvInputInfoComparator(this);
138     }
139 
start()140     public void start() {
141         if (mStarted) {
142             return;
143         }
144         if (DEBUG) Log.d(TAG, "start");
145         mStarted = true;
146         mTvInputManager.registerCallback(mInternalCallback, mHandler);
147         mInputMap.clear();
148         mInputStateMap.clear();
149         mInputIdToPartnerInputMap.clear();
150         for (TvInputInfo input : mTvInputManager.getTvInputList()) {
151             if (DEBUG) Log.d(TAG, "Input detected " + input);
152             String inputId = input.getId();
153             if (isInBlackList(inputId)) {
154                 continue;
155             }
156             mInputMap.put(inputId, input);
157             int state = mTvInputManager.getInputState(inputId);
158             mInputStateMap.put(inputId, state);
159             mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
160         }
161         SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG,
162                 "mInputStateMap not the same size as mInputMap");
163         mContentRatingsManager.update();
164     }
165 
stop()166     public void stop() {
167         if (!mStarted) {
168             return;
169         }
170         mTvInputManager.unregisterCallback(mInternalCallback);
171         mStarted = false;
172         mInputStateMap.clear();
173         mInputMap.clear();
174         mInputIdToPartnerInputMap.clear();
175     }
176 
getTvInputInfos(boolean availableOnly, boolean tunerOnly)177     public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
178         ArrayList<TvInputInfo> list = new ArrayList<>();
179         for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
180             if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) {
181                 continue;
182             }
183             TvInputInfo input = getTvInputInfo(pair.getKey());
184             if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) {
185                 continue;
186             }
187             list.add(input);
188         }
189         Collections.sort(list, mTvInputInfoComparator);
190         return list;
191     }
192 
193     /**
194      * Returns the default comparator for {@link TvInputInfo}.
195      * See {@link TvInputInfoComparator} for detail.
196      */
getDefaultTvInputInfoComparator()197     public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
198         return mTvInputInfoComparator;
199     }
200 
201     /**
202      * Checks if the input is from a partner.
203      *
204      * It's visible for comparator test.
205      * Package private is enough for this method, but public is necessary to workaround mockito
206      * bug.
207      */
208     @VisibleForTesting
isPartnerInput(TvInputInfo inputInfo)209     public boolean isPartnerInput(TvInputInfo inputInfo) {
210         return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
211     }
212 
213     /**
214      * Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set.
215      */
isSystemInput(TvInputInfo inputInfo)216     public boolean isSystemInput(TvInputInfo inputInfo) {
217         return inputInfo != null
218                 && (inputInfo.getServiceInfo().applicationInfo.flags
219                     & ApplicationInfo.FLAG_SYSTEM) != 0;
220     }
221 
222     /**
223      * Is the input one known bundled inputs not written by OEM/SOCs.
224      */
isBundledInput(TvInputInfo inputInfo)225     public boolean isBundledInput(TvInputInfo inputInfo) {
226         return inputInfo != null && Utils.isInBundledPackageSet(inputInfo.getServiceInfo()
227                 .applicationInfo.packageName);
228     }
229 
230     /**
231      * Returns if the given input is bundled and written by OEM/SOCs.
232      * This returns the cached result.
233      */
isPartnerInput(String inputId)234     public boolean isPartnerInput(String inputId) {
235         Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId);
236         return (isPartnerInput != null) ? isPartnerInput : false;
237     }
238 
239     /**
240      * Loads label of {@code info}.
241      *
242      * It's visible for comparator test to mock TvInputInfo.
243      * Package private is enough for this method, but public is necessary to workaround mockito
244      * bug.
245      */
246     @VisibleForTesting
loadLabel(TvInputInfo info)247     public String loadLabel(TvInputInfo info) {
248         return info.loadLabel(mContext).toString();
249     }
250 
251     /**
252      * Returns if TV input exists with the input id.
253      */
hasTvInputInfo(String inputId)254     public boolean hasTvInputInfo(String inputId) {
255         SoftPreconditions.checkState(mStarted, TAG,
256                 "hasTvInputInfo() called before TvInputManagerHelper was started.");
257         return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
258     }
259 
getTvInputInfo(String inputId)260     public TvInputInfo getTvInputInfo(String inputId) {
261         SoftPreconditions.checkState(mStarted, TAG,
262                 "getTvInputInfo() called before TvInputManagerHelper was started.");
263         if (!mStarted) {
264             return null;
265         }
266         if (inputId == null) {
267             return null;
268         }
269         return mInputMap.get(inputId);
270     }
271 
getTvInputAppInfo(String inputId)272     public ApplicationInfo getTvInputAppInfo(String inputId) {
273         TvInputInfo info = getTvInputInfo(inputId);
274         return info == null ? null : info.getServiceInfo().applicationInfo;
275     }
276 
getTunerTvInputSize()277     public int getTunerTvInputSize() {
278         int size = 0;
279         for (TvInputInfo input : mInputMap.values()) {
280             if (input.getType() == TvInputInfo.TYPE_TUNER) {
281                 ++size;
282             }
283         }
284         return size;
285     }
286 
getInputState(TvInputInfo inputInfo)287     public int getInputState(TvInputInfo inputInfo) {
288         return getInputState(inputInfo.getId());
289     }
290 
getInputState(String inputId)291     public int getInputState(String inputId) {
292         SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
293         if (!mStarted) {
294             return TvInputManager.INPUT_STATE_DISCONNECTED;
295 
296         }
297         Integer state = mInputStateMap.get(inputId);
298         if (state == null) {
299             Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
300             return TvInputManager.INPUT_STATE_DISCONNECTED;
301         }
302         return state;
303     }
304 
addCallback(TvInputCallback callback)305     public void addCallback(TvInputCallback callback) {
306         mCallbacks.add(callback);
307     }
308 
removeCallback(TvInputCallback callback)309     public void removeCallback(TvInputCallback callback) {
310         mCallbacks.remove(callback);
311     }
312 
getParentalControlSettings()313     public ParentalControlSettings getParentalControlSettings() {
314         return mParentalControlSettings;
315     }
316 
317     /**
318      * Returns a ContentRatingsManager instance for a given application context.
319      */
getContentRatingsManager()320     public ContentRatingsManager getContentRatingsManager() {
321         return mContentRatingsManager;
322     }
323 
isInBlackList(String inputId)324     private boolean isInBlackList(String inputId) {
325         if (!Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
326             return false;
327         }
328         for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
329             if (inputId.contains(disabledTunerInputPrefix)) {
330                 return true;
331             }
332         }
333         return false;
334     }
335 
336     /**
337      * Default comparator for TvInputInfo.
338      *
339      * It's static class that accepts {@link TvInputManagerHelper} as parameter to test.
340      * To test comparator, we need to mock API in parent class such as {@link #isPartnerInput},
341      * but it's impossible for an inner class to use mocked methods.
342      * (i.e. Mockito's spy doesn't work)
343      */
344     @VisibleForTesting
345     static class TvInputInfoComparator implements Comparator<TvInputInfo> {
346         private final TvInputManagerHelper mInputManager;
347 
TvInputInfoComparator(TvInputManagerHelper inputManager)348         public TvInputInfoComparator(TvInputManagerHelper inputManager) {
349             mInputManager = inputManager;
350         }
351 
352         @Override
compare(TvInputInfo lhs, TvInputInfo rhs)353         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
354             if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
355                 return mInputManager.isPartnerInput(lhs) ? -1 : 1;
356             }
357             return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
358         }
359     }
360 }
361