Home | History | Annotate | Download | only in util
      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 {
     55         TvInputInfo getTvInputInfo(String inputId);
     56 
     57         Integer getInputState(String inputId);
     58 
     59         void registerCallback(TvInputCallback internalCallback, Handler handler);
     60 
     61         void unregisterCallback(TvInputCallback internalCallback);
     62 
     63         List<TvInputInfo> getTvInputList();
     64 
     65         List<TvContentRatingSystemInfo> getTvContentRatingSystemList();
     66     }
     67 
     68     private static final class TvInputManagerImpl implements TvInputManagerInterface {
     69         private final TvInputManager delegate;
     70 
     71         private TvInputManagerImpl(TvInputManager delegate) {
     72             this.delegate = delegate;
     73         }
     74 
     75         @Override
     76         public TvInputInfo getTvInputInfo(String inputId) {
     77             return delegate.getTvInputInfo(inputId);
     78         }
     79 
     80         @Override
     81         public Integer getInputState(String inputId) {
     82             return delegate.getInputState(inputId);
     83         }
     84 
     85         @Override
     86         public void registerCallback(TvInputCallback internalCallback, Handler handler) {
     87             delegate.registerCallback(internalCallback, handler);
     88         }
     89 
     90         @Override
     91         public void unregisterCallback(TvInputCallback internalCallback) {
     92             delegate.unregisterCallback(internalCallback);
     93         }
     94 
     95         @Override
     96         public List<TvInputInfo> getTvInputList() {
     97             return delegate.getTvInputList();
     98         }
     99 
    100         @Override
    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 
    268     public TvInputManagerHelper(Context context) {
    269         this(context, createTvInputManagerWrapper(context));
    270     }
    271 
    272     @Nullable
    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
    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 
    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 
    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. */
    345     public void clearTvInputLabels() {
    346         mTvInputLabels.clear();
    347         mTvInputCustomLabels.clear();
    348         mTvInputApplicationLabels.clear();
    349     }
    350 
    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      */
    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
    382     public boolean isPartnerInput(TvInputInfo inputInfo) {
    383         return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
    384     }
    385 
    386     /** Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. */
    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. */
    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      */
    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      */
    414     public boolean hasTvInputManager() {
    415         return mTvInputManager != null;
    416     }
    417 
    418     /** Loads label of {@code 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} */
    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. */
    442     public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
    443         return mTvInputApplicationLabels.get(inputId);
    444     }
    445 
    446     /** Stores the tv input application's label. */
    447     public void setTvInputApplicationLabel(String inputId, CharSequence label) {
    448         mTvInputApplicationLabels.put(inputId, label);
    449     }
    450 
    451     /** Gets the tv input application's icon. */
    452     public Drawable getTvInputApplicationIcon(String inputId) {
    453         return mTvInputApplicationIcons.get(inputId);
    454     }
    455 
    456     /** Stores the tv input application's icon. */
    457     public void setTvInputApplicationIcon(String inputId, Drawable icon) {
    458         mTvInputApplicationIcons.put(inputId, icon);
    459     }
    460 
    461     /** Gets the tv input application's banner. */
    462     public Drawable getTvInputApplicationBanner(String inputId) {
    463         return mTvInputAppliactionBanners.get(inputId);
    464     }
    465 
    466     /** Stores the tv input application's 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. */
    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 
    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 
    490     public ApplicationInfo getTvInputAppInfo(String inputId) {
    491         TvInputInfo info = getTvInputInfo(inputId);
    492         return info == null ? null : info.getServiceInfo().applicationInfo;
    493     }
    494 
    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      */
    511     public int getInputState(@Nullable TvInputInfo inputInfo) {
    512         return inputInfo == null
    513                 ? TvInputManager.INPUT_STATE_DISCONNECTED
    514                 : getInputState(inputInfo.getId());
    515     }
    516 
    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 
    530     public void addCallback(TvInputCallback callback) {
    531         mCallbacks.add(callback);
    532     }
    533 
    534     public void removeCallback(TvInputCallback callback) {
    535         mCallbacks.remove(callback);
    536     }
    537 
    538     public ParentalControlSettings getParentalControlSettings() {
    539         return mParentalControlSettings;
    540     }
    541 
    542     /** Returns a ContentRatingsManager instance for a given application context. */
    543     public ContentRatingsManager getContentRatingsManager() {
    544         return mContentRatingsManager;
    545     }
    546 
    547     private int getInputSortKey(TvInputInfo input) {
    548         return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, Integer.MAX_VALUE);
    549     }
    550 
    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 
    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 
    615         public InputComparatorInternal(TvInputManagerHelper inputManager) {
    616             mInputManager = inputManager;
    617         }
    618 
    619         @Override
    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 
    637         public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) {
    638             mContext = context;
    639             mTvInputManagerHelper = tvInputManagerHelper;
    640             setupDeviceTypePriorities();
    641         }
    642 
    643         @Override
    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 
    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 
    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 
    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 
    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