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