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