• 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.content.pm.PackageManager;
22 import android.database.ContentObserver;
23 import android.graphics.drawable.Drawable;
24 import android.hardware.hdmi.HdmiDeviceInfo;
25 import android.media.tv.TvContentRatingSystemInfo;
26 import android.media.tv.TvInputInfo;
27 import android.media.tv.TvInputManager;
28 import android.media.tv.TvInputManager.TvInputCallback;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.provider.Settings;
32 import android.provider.Settings.SettingNotFoundException;
33 import android.support.annotation.Nullable;
34 import android.support.annotation.UiThread;
35 import android.support.annotation.VisibleForTesting;
36 import android.text.TextUtils;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import com.android.tv.common.SoftPreconditions;
40 import com.android.tv.common.compat.TvInputInfoCompat;
41 import com.android.tv.common.dagger.annotations.ApplicationContext;
42 import com.android.tv.common.util.CommonUtils;
43 import com.android.tv.common.util.SystemProperties;
44 import com.android.tv.features.TvFeatures;
45 import com.android.tv.parental.ContentRatingsManager;
46 import com.android.tv.parental.ParentalControlSettings;
47 import com.android.tv.util.images.ImageCache;
48 import com.android.tv.util.images.ImageLoader;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import javax.inject.Inject;
58 import javax.inject.Singleton;
59 
60 /** Helper class for {@link TvInputManager}. */
61 @UiThread
62 @Singleton
63 public class TvInputManagerHelper {
64     private static final String TAG = "TvInputManagerHelper";
65     private static final boolean DEBUG = false;
66 
67     public interface TvInputManagerInterface {
getTvInputInfo(String inputId)68         TvInputInfo getTvInputInfo(String inputId);
69 
getInputState(String inputId)70         Integer getInputState(String inputId);
71 
registerCallback(TvInputCallback internalCallback, Handler handler)72         void registerCallback(TvInputCallback internalCallback, Handler handler);
73 
unregisterCallback(TvInputCallback internalCallback)74         void unregisterCallback(TvInputCallback internalCallback);
75 
getTvInputList()76         List<TvInputInfo> getTvInputList();
77 
getTvContentRatingSystemList()78         List<TvContentRatingSystemInfo> getTvContentRatingSystemList();
79     }
80 
81     private static final class TvInputManagerImpl implements TvInputManagerInterface {
82         private final TvInputManager delegate;
83 
TvInputManagerImpl(TvInputManager delegate)84         private TvInputManagerImpl(TvInputManager delegate) {
85             this.delegate = delegate;
86         }
87 
88         @Override
getTvInputInfo(String inputId)89         public TvInputInfo getTvInputInfo(String inputId) {
90             return delegate.getTvInputInfo(inputId);
91         }
92 
93         @Override
getInputState(String inputId)94         public Integer getInputState(String inputId) {
95             return delegate.getInputState(inputId);
96         }
97 
98         @Override
registerCallback(TvInputCallback internalCallback, Handler handler)99         public void registerCallback(TvInputCallback internalCallback, Handler handler) {
100             delegate.registerCallback(internalCallback, handler);
101         }
102 
103         @Override
unregisterCallback(TvInputCallback internalCallback)104         public void unregisterCallback(TvInputCallback internalCallback) {
105             delegate.unregisterCallback(internalCallback);
106         }
107 
108         @Override
getTvInputList()109         public List<TvInputInfo> getTvInputList() {
110             return delegate.getTvInputList();
111         }
112 
113         @Override
getTvContentRatingSystemList()114         public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
115             return delegate.getTvContentRatingSystemList();
116         }
117     }
118 
119     /** Types of HDMI device and bundled tuner. */
120     public static final int TYPE_CEC_DEVICE = -2;
121 
122     public static final int TYPE_BUNDLED_TUNER = -3;
123     public static final int TYPE_CEC_DEVICE_RECORDER = -4;
124     public static final int TYPE_CEC_DEVICE_PLAYBACK = -5;
125     public static final int TYPE_MHL_MOBILE = -6;
126 
127     private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
128             "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
129     private static final String[] mPhysicalTunerBlackList = {
130     };
131     private static final String META_LABEL_SORT_KEY = "input_sort_key";
132 
133     private static final String TV_INPUT_ALLOW_3RD_PARTY_INPUTS = "tv_input_allow_3rd_party_inputs";
134 
135     private static final String[] SYSTEM_INPUT_ID_BLACKLIST = {
136         "com.google.android.videos/" // Play Movies
137     };
138 
139     /** The default tv input priority to show. */
140     private static final ArrayList<Integer> DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>();
141 
142     static {
143         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER);
144         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER);
145         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE);
146         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER);
147         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK);
148         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE);
149         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI);
150         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI);
151         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT);
152         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO);
153         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE);
154         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT);
155         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA);
156         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART);
157         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER);
158     }
159 
160     private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
161     };
162 
163     private static final String[] TESTABLE_INPUTS = {
164         "com.android.tv.testinput/.TestTvInputService"
165     };
166 
167     private final Context mContext;
168     private final PackageManager mPackageManager;
169     protected final TvInputManagerInterface mTvInputManager;
170     private final Map<String, Integer> mInputStateMap = new HashMap<>();
171     private final Map<String, TvInputInfoCompat> mInputMap = new HashMap<>();
172     private final Map<String, String> mTvInputLabels = new ArrayMap<>();
173     private final Map<String, String> mTvInputCustomLabels = new ArrayMap<>();
174     private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
175 
176     private final Map<String, CharSequence> mTvInputApplicationLabels = new ArrayMap<>();
177     private final Map<String, Drawable> mTvInputApplicationIcons = new ArrayMap<>();
178     private final Map<String, Drawable> mTvInputApplicationBanners = new ArrayMap<>();
179 
180     private final ContentObserver mContentObserver;
181 
182     private final TvInputCallback mInternalCallback =
183             new TvInputCallback() {
184                 @Override
185                 public void onInputStateChanged(String inputId, int state) {
186                     if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
187                     TvInputInfo info = mInputMap.get(inputId).getTvInputInfo();
188                     if (info == null || isInputBlocked(info)) {
189                         return;
190                     }
191                     mInputStateMap.put(inputId, state);
192                     for (TvInputCallback callback : mCallbacks) {
193                         callback.onInputStateChanged(inputId, state);
194                     }
195                 }
196 
197                 @Override
198                 public void onInputAdded(String inputId) {
199                     if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
200                     TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
201                     if (info == null || isInputBlocked(info)) {
202                         return;
203                     }
204                     if (info != null) {
205                         mInputMap.put(inputId, new TvInputInfoCompat(mContext, info));
206                         CharSequence label = info.loadLabel(mContext);
207                         // in tests the label may be missing just use the input id
208                         mTvInputLabels.put(inputId, label != null ? label.toString() : inputId);
209                         CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
210                         if (inputCustomLabel != null) {
211                             mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
212                         }
213                         mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
214                         mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
215                     }
216                     mContentRatingsManager.update();
217                     for (TvInputCallback callback : mCallbacks) {
218                         callback.onInputAdded(inputId);
219                     }
220                 }
221 
222                 @Override
223                 public void onInputRemoved(String inputId) {
224                     if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
225                     mInputMap.remove(inputId);
226                     mTvInputLabels.remove(inputId);
227                     mTvInputCustomLabels.remove(inputId);
228                     mTvInputApplicationLabels.remove(inputId);
229                     mTvInputApplicationIcons.remove(inputId);
230                     mTvInputApplicationBanners.remove(inputId);
231                     mInputStateMap.remove(inputId);
232                     mInputIdToPartnerInputMap.remove(inputId);
233                     mContentRatingsManager.update();
234                     for (TvInputCallback callback : mCallbacks) {
235                         callback.onInputRemoved(inputId);
236                     }
237                     ImageCache.getInstance()
238                             .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId));
239                 }
240 
241                 @Override
242                 public void onInputUpdated(String inputId) {
243                     if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
244                     TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
245                     if (info == null || isInputBlocked(info)) {
246                         return;
247                     }
248                     mInputMap.put(inputId, new TvInputInfoCompat(mContext, info));
249                     mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
250                     CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
251                     if (inputCustomLabel != null) {
252                         mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
253                     }
254                     mTvInputApplicationLabels.remove(inputId);
255                     mTvInputApplicationIcons.remove(inputId);
256                     mTvInputApplicationBanners.remove(inputId);
257                     for (TvInputCallback callback : mCallbacks) {
258                         callback.onInputUpdated(inputId);
259                     }
260                     ImageCache.getInstance()
261                             .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId));
262                 }
263 
264                 @Override
265                 public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
266                     if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
267                     if (isInputBlocked(inputInfo)) {
268                         return;
269                     }
270                     mInputMap.put(inputInfo.getId(), new TvInputInfoCompat(mContext, inputInfo));
271                     mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString());
272                     CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);
273                     if (inputCustomLabel != null) {
274                         mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString());
275                     }
276                     for (TvInputCallback callback : mCallbacks) {
277                         callback.onTvInputInfoUpdated(inputInfo);
278                     }
279                     ImageCache.getInstance()
280                             .remove(
281                                     ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
282                                             inputInfo.getId()));
283                 }
284             };
285 
286     private final Handler mHandler = new Handler();
287     private boolean mStarted;
288     private final HashSet<TvInputCallback> mCallbacks = new HashSet<>();
289     private final ContentRatingsManager mContentRatingsManager;
290     private final ParentalControlSettings mParentalControlSettings;
291     private final Comparator<TvInputInfo> mTvInputInfoComparator;
292     private boolean mAllow3rdPartyInputs;
293 
294     @Inject
TvInputManagerHelper(@pplicationContext Context context)295     public TvInputManagerHelper(@ApplicationContext Context context) {
296         this(context, createTvInputManagerWrapper(context));
297     }
298 
299     @Nullable
createTvInputManagerWrapper(Context context)300     protected static TvInputManagerImpl createTvInputManagerWrapper(Context context) {
301         TvInputManager tvInputManager =
302                 (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
303         return tvInputManager == null ? null : new TvInputManagerImpl(tvInputManager);
304     }
305 
306     @VisibleForTesting
TvInputManagerHelper( Context context, @Nullable TvInputManagerInterface tvInputManager)307     protected TvInputManagerHelper(
308             Context context, @Nullable TvInputManagerInterface tvInputManager) {
309         mContext = context.getApplicationContext();
310         mPackageManager = context.getPackageManager();
311         mTvInputManager = tvInputManager;
312         mContentRatingsManager = new ContentRatingsManager(context, tvInputManager);
313         mParentalControlSettings = new ParentalControlSettings(context);
314         mTvInputInfoComparator = new InputComparatorInternal(this);
315         mContentObserver =
316                 new ContentObserver(mHandler) {
317                     @Override
318                     public void onChange(boolean selfChange, Uri uri) {
319                         String option = uri.getLastPathSegment();
320                         if (option == null || !option.equals(TV_INPUT_ALLOW_3RD_PARTY_INPUTS)) {
321                             return;
322                         }
323                         boolean previousSetting = mAllow3rdPartyInputs;
324                         updateAllow3rdPartyInputs();
325                         if (previousSetting == mAllow3rdPartyInputs) {
326                             return;
327                         }
328                         initInputMaps();
329                     }
330                 };
331     }
332 
start()333     public void start() {
334         if (!hasTvInputManager()) {
335             // Not a TV device
336             return;
337         }
338         if (mStarted) {
339             return;
340         }
341         if (DEBUG) Log.d(TAG, "start");
342         mStarted = true;
343         mContext.getContentResolver()
344                 .registerContentObserver(
345                         Settings.Global.getUriFor(TV_INPUT_ALLOW_3RD_PARTY_INPUTS),
346                         true,
347                         mContentObserver);
348         updateAllow3rdPartyInputs();
349         mTvInputManager.registerCallback(mInternalCallback, mHandler);
350         initInputMaps();
351         mContentRatingsManager.update();
352     }
353 
stop()354     public void stop() {
355         if (!mStarted) {
356             return;
357         }
358         mTvInputManager.unregisterCallback(mInternalCallback);
359         mContext.getContentResolver().unregisterContentObserver(mContentObserver);
360         mStarted = false;
361         mInputStateMap.clear();
362         mInputMap.clear();
363         mTvInputLabels.clear();
364         mTvInputCustomLabels.clear();
365         mTvInputApplicationLabels.clear();
366         mTvInputApplicationIcons.clear();
367         mTvInputApplicationBanners.clear();
368         mInputIdToPartnerInputMap.clear();
369     }
370 
371     /** Clears the TvInput labels map. */
clearTvInputLabels()372     public void clearTvInputLabels() {
373         mTvInputLabels.clear();
374         mTvInputCustomLabels.clear();
375         mTvInputApplicationLabels.clear();
376     }
377 
getTvInputInfos(boolean availableOnly, boolean tunerOnly)378     public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
379         ArrayList<TvInputInfo> list = new ArrayList<>();
380         for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
381             if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) {
382                 continue;
383             }
384             TvInputInfo input = getTvInputInfo(pair.getKey());
385             if (input == null || isInputBlocked(input)) {
386                 continue;
387             }
388             if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) {
389                 continue;
390             }
391             list.add(input);
392         }
393         Collections.sort(list, mTvInputInfoComparator);
394         return list;
395     }
396 
397     /**
398      * Returns the default comparator for {@link TvInputInfo}. See {@link InputComparatorInternal}
399      * for detail.
400      */
getDefaultTvInputInfoComparator()401     public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
402         return mTvInputInfoComparator;
403     }
404 
405     /**
406      * Checks if the input is from a partner.
407      *
408      * <p>It's visible for comparator test. Package private is enough for this method, but public is
409      * necessary to workaround mockito bug.
410      */
411     @VisibleForTesting
isPartnerInput(TvInputInfo inputInfo)412     public boolean isPartnerInput(TvInputInfo inputInfo) {
413         return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
414     }
415 
416     /** Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. */
isSystemInput(TvInputInfo inputInfo)417     public boolean isSystemInput(TvInputInfo inputInfo) {
418         return inputInfo != null
419                 && (inputInfo.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
420                         != 0;
421     }
422 
423     /** Is the input one known bundled inputs not written by OEM/SOCs. */
isBundledInput(TvInputInfo inputInfo)424     public boolean isBundledInput(TvInputInfo inputInfo) {
425         return inputInfo != null
426                 && CommonUtils.isInBundledPackageSet(
427                         inputInfo.getServiceInfo().applicationInfo.packageName);
428     }
429 
430     /**
431      * Returns if the given input is bundled and written by OEM/SOCs. This returns the cached
432      * result.
433      */
isPartnerInput(String inputId)434     public boolean isPartnerInput(String inputId) {
435         Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId);
436         return (isPartnerInput != null) ? isPartnerInput : false;
437     }
438 
439     /**
440      * Is (Context.TV_INPUT_SERVICE) available.
441      *
442      * <p>This is only available on TV devices.
443      */
hasTvInputManager()444     public boolean hasTvInputManager() {
445         return mTvInputManager != null;
446     }
447 
448     /** Loads label of {@code info}. */
loadLabel(TvInputInfo info)449     public String loadLabel(TvInputInfo info) {
450         String label = mTvInputLabels.get(info.getId());
451         if (label == null) {
452             label = info.loadLabel(mContext).toString();
453             mTvInputLabels.put(info.getId(), label);
454         }
455         return label;
456     }
457 
458     /** Loads custom label of {@code info} */
loadCustomLabel(TvInputInfo info)459     public String loadCustomLabel(TvInputInfo info) {
460         String customLabel = mTvInputCustomLabels.get(info.getId());
461         if (customLabel == null) {
462             CharSequence customLabelCharSequence = info.loadCustomLabel(mContext);
463             if (customLabelCharSequence != null) {
464                 customLabel = customLabelCharSequence.toString();
465                 mTvInputCustomLabels.put(info.getId(), customLabel);
466             }
467         }
468         return customLabel;
469     }
470 
471     /** Gets the tv input application's label. */
getTvInputApplicationLabel(CharSequence inputId)472     public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
473         return mTvInputApplicationLabels.get(inputId);
474     }
475 
476     /** Stores the tv input application's label. */
setTvInputApplicationLabel(String inputId, CharSequence label)477     public void setTvInputApplicationLabel(String inputId, CharSequence label) {
478         mTvInputApplicationLabels.put(inputId, label);
479     }
480 
481     /** Gets the tv input application's icon. */
getTvInputApplicationIcon(String inputId)482     public Drawable getTvInputApplicationIcon(String inputId) {
483         return mTvInputApplicationIcons.get(inputId);
484     }
485 
486     /** Stores the tv input application's icon. */
setTvInputApplicationIcon(String inputId, Drawable icon)487     public void setTvInputApplicationIcon(String inputId, Drawable icon) {
488         mTvInputApplicationIcons.put(inputId, icon);
489     }
490 
491     /** Gets the tv input application's banner. */
getTvInputApplicationBanner(String inputId)492     public Drawable getTvInputApplicationBanner(String inputId) {
493         return mTvInputApplicationBanners.get(inputId);
494     }
495 
496     /** Stores the tv input application's banner. */
setTvInputApplicationBanner(String inputId, Drawable banner)497     public void setTvInputApplicationBanner(String inputId, Drawable banner) {
498         mTvInputApplicationBanners.put(inputId, banner);
499     }
500 
501     /** Returns if TV input exists with the input id. */
hasTvInputInfo(String inputId)502     public boolean hasTvInputInfo(String inputId) {
503         SoftPreconditions.checkState(
504                 mStarted, TAG, "hasTvInputInfo() called before TvInputManagerHelper was started.");
505         return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
506     }
507 
508     @Nullable
getTvInputInfo(String inputId)509     public TvInputInfo getTvInputInfo(String inputId) {
510         TvInputInfoCompat inputInfo = getTvInputInfoCompat(inputId);
511         return inputInfo == null ? null : inputInfo.getTvInputInfo();
512     }
513 
514     @Nullable
getTvInputInfoCompat(String inputId)515     public TvInputInfoCompat getTvInputInfoCompat(String inputId) {
516         SoftPreconditions.checkState(
517                 mStarted, TAG, "getTvInputInfo() called before TvInputManagerHelper was started.");
518         if (!mStarted) {
519             return null;
520         }
521         if (inputId == null) {
522             return null;
523         }
524         return mInputMap.get(inputId);
525     }
526 
getTvInputAppInfo(String inputId)527     public ApplicationInfo getTvInputAppInfo(String inputId) {
528         TvInputInfo info = getTvInputInfo(inputId);
529         return info == null ? null : info.getServiceInfo().applicationInfo;
530     }
531 
getTunerTvInputSize()532     public int getTunerTvInputSize() {
533         int size = 0;
534         for (TvInputInfoCompat input : mInputMap.values()) {
535             if (input.getType() == TvInputInfo.TYPE_TUNER) {
536                 ++size;
537             }
538         }
539         return size;
540     }
541     /**
542      * Returns TvInputInfo's input state.
543      *
544      * @param inputInfo
545      * @return An Integer which stands for the input state {@link
546      *     TvInputManager.INPUT_STATE_DISCONNECTED} if inputInfo is null
547      */
getInputState(@ullable TvInputInfo inputInfo)548     public int getInputState(@Nullable TvInputInfo inputInfo) {
549         return inputInfo == null
550                 ? TvInputManager.INPUT_STATE_DISCONNECTED
551                 : getInputState(inputInfo.getId());
552     }
553 
getInputState(String inputId)554     public int getInputState(String inputId) {
555         SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
556         if (!mStarted) {
557             return TvInputManager.INPUT_STATE_DISCONNECTED;
558         }
559         Integer state = mInputStateMap.get(inputId);
560         if (state == null) {
561             Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
562             return TvInputManager.INPUT_STATE_DISCONNECTED;
563         }
564         return state;
565     }
566 
addCallback(TvInputCallback callback)567     public void addCallback(TvInputCallback callback) {
568         mCallbacks.add(callback);
569     }
570 
removeCallback(TvInputCallback callback)571     public void removeCallback(TvInputCallback callback) {
572         mCallbacks.remove(callback);
573     }
574 
getParentalControlSettings()575     public ParentalControlSettings getParentalControlSettings() {
576         return mParentalControlSettings;
577     }
578 
579     /** Returns a ContentRatingsManager instance for a given application context. */
getContentRatingsManager()580     public ContentRatingsManager getContentRatingsManager() {
581         return mContentRatingsManager;
582     }
583 
getInputSortKey(TvInputInfo input)584     private int getInputSortKey(TvInputInfo input) {
585         return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, Integer.MAX_VALUE);
586     }
587 
isInputPhysicalTuner(TvInputInfo input)588     private boolean isInputPhysicalTuner(TvInputInfo input) {
589         String packageName = input.getServiceInfo().packageName;
590         if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) {
591             return false;
592         }
593 
594         if (input.createSetupIntent() == null) {
595             return false;
596         } else {
597             boolean mayBeTunerInput =
598                     mPackageManager.checkPermission(
599                                     PERMISSION_ACCESS_ALL_EPG_DATA,
600                                     input.getServiceInfo().packageName)
601                             == PackageManager.PERMISSION_GRANTED;
602             if (!mayBeTunerInput) {
603                 try {
604                     ApplicationInfo ai =
605                             mPackageManager.getApplicationInfo(
606                                     input.getServiceInfo().packageName, 0);
607                     if ((ai.flags
608                                     & (ApplicationInfo.FLAG_SYSTEM
609                                             | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP))
610                             == 0) {
611                         return false;
612                     }
613                 } catch (PackageManager.NameNotFoundException e) {
614                     return false;
615                 }
616             }
617         }
618         return true;
619     }
620 
isInBlackList(String inputId)621     private boolean isInBlackList(String inputId) {
622         if (TvFeatures.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
623             for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
624                 if (inputId.contains(disabledTunerInputPrefix)) {
625                     return true;
626                 }
627             }
628         }
629         if (CommonUtils.isRoboTest()) return false;
630         if (CommonUtils.isRunningInTest()) {
631             for (String testableInput : TESTABLE_INPUTS) {
632                 if (testableInput.equals(inputId)) {
633                     return false;
634                 }
635             }
636             return true;
637         }
638         return false;
639     }
640 
initInputMaps()641     private void initInputMaps() {
642         mInputMap.clear();
643         mTvInputLabels.clear();
644         mTvInputCustomLabels.clear();
645         mTvInputApplicationLabels.clear();
646         mTvInputApplicationIcons.clear();
647         mTvInputApplicationBanners.clear();
648         mInputStateMap.clear();
649         mInputIdToPartnerInputMap.clear();
650         for (TvInputInfo input : mTvInputManager.getTvInputList()) {
651             if (DEBUG) {
652                 Log.d(TAG, "Input detected " + input);
653             }
654             String inputId = input.getId();
655             if (isInputBlocked(input)) {
656                 continue;
657             }
658             mInputMap.put(inputId, new TvInputInfoCompat(mContext, input));
659             int state = mTvInputManager.getInputState(inputId);
660             mInputStateMap.put(inputId, state);
661             mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
662         }
663         SoftPreconditions.checkState(
664                 mInputStateMap.size() == mInputMap.size(),
665                 TAG,
666                 "mInputStateMap not the same size as mInputMap");
667     }
668 
updateAllow3rdPartyInputs()669     private void updateAllow3rdPartyInputs() {
670         int setting;
671         try {
672             setting =
673                     Settings.Global.getInt(
674                             mContext.getContentResolver(), TV_INPUT_ALLOW_3RD_PARTY_INPUTS);
675         } catch (SettingNotFoundException e) {
676             mAllow3rdPartyInputs = SystemProperties.ALLOW_THIRD_PARTY_INPUTS.getValue();
677             return;
678         }
679         mAllow3rdPartyInputs = setting == 1;
680     }
681 
isInputBlocked(TvInputInfo info)682     private boolean isInputBlocked(TvInputInfo info) {
683         if (!mAllow3rdPartyInputs) {
684             if (!isSystemInput(info)) {
685                 return true;
686             }
687             for (String id : SYSTEM_INPUT_ID_BLACKLIST) {
688                 if (info.getId().startsWith(id)) {
689                     return true;
690                 }
691             }
692         }
693         return isInBlackList(info.getId());
694     }
695 
696     /**
697      * Default comparator for TvInputInfo.
698      *
699      * <p>It's static class that accepts {@link TvInputManagerHelper} as parameter to test. To test
700      * comparator, we need to mock API in parent class such as {@link #isPartnerInput}, but it's
701      * impossible for an inner class to use mocked methods. (i.e. Mockito's spy doesn't work)
702      */
703     @VisibleForTesting
704     static class InputComparatorInternal implements Comparator<TvInputInfo> {
705         private final TvInputManagerHelper mInputManager;
706 
InputComparatorInternal(TvInputManagerHelper inputManager)707         public InputComparatorInternal(TvInputManagerHelper inputManager) {
708             mInputManager = inputManager;
709         }
710 
711         @Override
compare(TvInputInfo lhs, TvInputInfo rhs)712         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
713             if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
714                 return mInputManager.isPartnerInput(lhs) ? -1 : 1;
715             }
716             return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
717         }
718     }
719 
720     /**
721      * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of TV
722      * inputs.
723      */
724     public static class HardwareInputComparator implements Comparator<TvInputInfo> {
725         private Map<Integer, Integer> mTypePriorities = new HashMap<>();
726         private final TvInputManagerHelper mTvInputManagerHelper;
727         private final Context mContext;
728 
HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper)729         public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) {
730             mContext = context;
731             mTvInputManagerHelper = tvInputManagerHelper;
732             setupDeviceTypePriorities();
733         }
734 
735         @Override
compare(TvInputInfo lhs, TvInputInfo rhs)736         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
737             if (lhs == null) {
738                 return (rhs == null) ? 0 : 1;
739             }
740             if (rhs == null) {
741                 return -1;
742             }
743 
744             boolean enabledL =
745                     (mTvInputManagerHelper.getInputState(lhs)
746                             != TvInputManager.INPUT_STATE_DISCONNECTED);
747             boolean enabledR =
748                     (mTvInputManagerHelper.getInputState(rhs)
749                             != TvInputManager.INPUT_STATE_DISCONNECTED);
750             if (enabledL != enabledR) {
751                 return enabledL ? -1 : 1;
752             }
753 
754             int priorityL = getPriority(lhs);
755             int priorityR = getPriority(rhs);
756             if (priorityL != priorityR) {
757                 return priorityL - priorityR;
758             }
759 
760             if (lhs.getType() == TvInputInfo.TYPE_TUNER
761                     && rhs.getType() == TvInputInfo.TYPE_TUNER) {
762                 boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs);
763                 boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs);
764                 if (isPhysicalL != isPhysicalR) {
765                     return isPhysicalL ? -1 : 1;
766                 }
767             }
768 
769             int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs);
770             int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs);
771             if (sortKeyL != sortKeyR) {
772                 return sortKeyR - sortKeyL;
773             }
774 
775             String parentLabelL =
776                     lhs.getParentId() != null
777                             ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId()))
778                             : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId()));
779             String parentLabelR =
780                     rhs.getParentId() != null
781                             ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId()))
782                             : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId()));
783 
784             if (!TextUtils.equals(parentLabelL, parentLabelR)) {
785                 return parentLabelL.compareToIgnoreCase(parentLabelR);
786             }
787             return getLabel(lhs).compareToIgnoreCase(getLabel(rhs));
788         }
789 
getLabel(TvInputInfo input)790         private String getLabel(TvInputInfo input) {
791             if (input == null) {
792                 return "";
793             }
794             String label = mTvInputManagerHelper.loadCustomLabel(input);
795             if (TextUtils.isEmpty(label)) {
796                 label = mTvInputManagerHelper.loadLabel(input);
797             }
798             return label;
799         }
800 
getPriority(TvInputInfo info)801         private int getPriority(TvInputInfo info) {
802             Integer priority = null;
803             if (mTypePriorities != null) {
804                 priority = mTypePriorities.get(getTvInputTypeForPriority(info));
805             }
806             if (priority != null) {
807                 return priority;
808             }
809             return Integer.MAX_VALUE;
810         }
811 
setupDeviceTypePriorities()812         private void setupDeviceTypePriorities() {
813             mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap();
814 
815             // Fill in any missing priorities in the map we got from the OEM
816             int priority = mTypePriorities.size();
817             for (int type : DEFAULT_TV_INPUT_PRIORITY) {
818                 if (!mTypePriorities.containsKey(type)) {
819                     mTypePriorities.put(type, priority++);
820                 }
821             }
822         }
823 
getTvInputTypeForPriority(TvInputInfo info)824         private int getTvInputTypeForPriority(TvInputInfo info) {
825             if (info.getHdmiDeviceInfo() != null) {
826                 if (info.getHdmiDeviceInfo().isCecDevice()) {
827                     switch (info.getHdmiDeviceInfo().getDeviceType()) {
828                         case HdmiDeviceInfo.DEVICE_RECORDER:
829                             return TYPE_CEC_DEVICE_RECORDER;
830                         case HdmiDeviceInfo.DEVICE_PLAYBACK:
831                             return TYPE_CEC_DEVICE_PLAYBACK;
832                         default:
833                             return TYPE_CEC_DEVICE;
834                     }
835                 } else if (info.getHdmiDeviceInfo().isMhlDevice()) {
836                     return TYPE_MHL_MOBILE;
837                 }
838             }
839             return info.getType();
840         }
841     }
842 }
843