/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.util; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; import android.net.Uri; import android.os.Handler; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.compat.TvInputInfoCompat; import com.android.tv.common.dagger.annotations.ApplicationContext; import com.android.tv.common.util.CommonUtils; import com.android.tv.common.util.SystemProperties; import com.android.tv.features.TvFeatures; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.images.ImageCache; import com.android.tv.util.images.ImageLoader; import com.google.common.collect.Ordering; import com.android.tv.common.flags.LegacyFlags; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; /** Helper class for {@link TvInputManager}. */ @UiThread @Singleton public class TvInputManagerHelper { private static final String TAG = "TvInputManagerHelper"; private static final boolean DEBUG = false; public interface TvInputManagerInterface { TvInputInfo getTvInputInfo(String inputId); Integer getInputState(String inputId); void registerCallback(TvInputCallback internalCallback, Handler handler); void unregisterCallback(TvInputCallback internalCallback); List getTvInputList(); List getTvContentRatingSystemList(); } private static final class TvInputManagerImpl implements TvInputManagerInterface { private final TvInputManager delegate; private TvInputManagerImpl(TvInputManager delegate) { this.delegate = delegate; } @Override public TvInputInfo getTvInputInfo(String inputId) { return delegate.getTvInputInfo(inputId); } @Override public Integer getInputState(String inputId) { return delegate.getInputState(inputId); } @Override public void registerCallback(TvInputCallback internalCallback, Handler handler) { delegate.registerCallback(internalCallback, handler); } @Override public void unregisterCallback(TvInputCallback internalCallback) { delegate.unregisterCallback(internalCallback); } @Override public List getTvInputList() { return delegate.getTvInputList(); } @Override public List getTvContentRatingSystemList() { return delegate.getTvContentRatingSystemList(); } } /** Types of HDMI device and bundled tuner. */ public static final int TYPE_CEC_DEVICE = -2; public static final int TYPE_BUNDLED_TUNER = -3; public static final int TYPE_CEC_DEVICE_RECORDER = -4; public static final int TYPE_CEC_DEVICE_PLAYBACK = -5; public static final int TYPE_MHL_MOBILE = -6; private static final String PERMISSION_ACCESS_ALL_EPG_DATA = "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; private static final String[] mPhysicalTunerBlockList = { "com.google.android.videos", // Play Movies }; private static final String META_LABEL_SORT_KEY = "input_sort_key"; private static final String TV_INPUT_ALLOW_3RD_PARTY_INPUTS = "tv_input_allow_3rd_party_inputs"; private static final String[] SYSTEM_INPUT_ID_BLOCKLIST = { "com.google.android.videos/" // Play Movies }; /** The default tv input priority to show. */ private static final ArrayList DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>(); static { DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER); DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE); DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER); DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK); DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER); } private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLOCKLIST = { /* Begin_AOSP_Comment_Out // Disabled partner's tuner input prefix list. "com.mediatek.tvinput/.dtv" End_AOSP_Comment_Out */ }; private static final String[] TESTABLE_INPUTS = { "com.android.tv.testinput/.TestTvInputService" }; private final Context mContext; private final PackageManager mPackageManager; protected final TvInputManagerInterface mTvInputManager; private final Map mInputStateMap = new HashMap<>(); private final Map mInputMap = new HashMap<>(); private final Map mTvInputLabels = new ArrayMap<>(); private final Map mTvInputCustomLabels = new ArrayMap<>(); private final Map mInputIdToPartnerInputMap = new HashMap<>(); private final Map mTvInputApplicationLabels = new ArrayMap<>(); private final Map mTvInputApplicationIcons = new ArrayMap<>(); private final Map mTvInputApplicationBanners = new ArrayMap<>(); private final ContentObserver mContentObserver; private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override public void onInputStateChanged(String inputId, int state) { if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state); TvInputInfo info = mInputMap.get(inputId).getTvInputInfo(); if (info == null || isInputBlocked(info)) { return; } mInputStateMap.put(inputId, state); for (TvInputCallback callback : mCallbacks) { callback.onInputStateChanged(inputId, state); } } @Override public void onInputAdded(String inputId) { if (DEBUG) Log.d(TAG, "onInputAdded " + inputId); TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); if (info == null || isInputBlocked(info)) { return; } if (info != null) { mInputMap.put(inputId, new TvInputInfoCompat(mContext, info)); CharSequence label = info.loadLabel(mContext); // in tests the label may be missing just use the input id mTvInputLabels.put(inputId, label != null ? label.toString() : inputId); CharSequence inputCustomLabel = info.loadCustomLabel(mContext); if (inputCustomLabel != null) { mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); } mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId)); mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info)); } mContentRatingsManager.update(); for (TvInputCallback callback : mCallbacks) { callback.onInputAdded(inputId); } } @Override public void onInputRemoved(String inputId) { if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); mInputMap.remove(inputId); mTvInputLabels.remove(inputId); mTvInputCustomLabels.remove(inputId); mTvInputApplicationLabels.remove(inputId); mTvInputApplicationIcons.remove(inputId); mTvInputApplicationBanners.remove(inputId); mInputStateMap.remove(inputId); mInputIdToPartnerInputMap.remove(inputId); mContentRatingsManager.update(); for (TvInputCallback callback : mCallbacks) { callback.onInputRemoved(inputId); } ImageCache.getInstance() .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId)); } @Override public void onInputUpdated(String inputId) { if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId); TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); if (info == null || isInputBlocked(info)) { return; } mInputMap.put(inputId, new TvInputInfoCompat(mContext, info)); mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); CharSequence inputCustomLabel = info.loadCustomLabel(mContext); if (inputCustomLabel != null) { mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); } mTvInputApplicationLabels.remove(inputId); mTvInputApplicationIcons.remove(inputId); mTvInputApplicationBanners.remove(inputId); for (TvInputCallback callback : mCallbacks) { callback.onInputUpdated(inputId); } ImageCache.getInstance() .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId)); } @Override public void onTvInputInfoUpdated(TvInputInfo inputInfo) { if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo); if (isInputBlocked(inputInfo)) { return; } mInputMap.put(inputInfo.getId(), new TvInputInfoCompat(mContext, inputInfo)); mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString()); CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext); if (inputCustomLabel != null) { mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString()); } for (TvInputCallback callback : mCallbacks) { callback.onTvInputInfoUpdated(inputInfo); } ImageCache.getInstance() .remove( ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey( inputInfo.getId())); } }; private final Handler mHandler = new Handler(); private boolean mStarted; private final HashSet mCallbacks = new HashSet<>(); private final ContentRatingsManager mContentRatingsManager; private final ParentalControlSettings mParentalControlSettings; private final Comparator mTvInputInfoComparator; private boolean mAllow3rdPartyInputs; @Inject public TvInputManagerHelper(@ApplicationContext Context context, LegacyFlags legacyFlags) { this(context, createTvInputManagerWrapper(context), legacyFlags); } @Nullable protected static TvInputManagerImpl createTvInputManagerWrapper(Context context) { TvInputManager tvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); return tvInputManager == null ? null : new TvInputManagerImpl(tvInputManager); } @VisibleForTesting protected TvInputManagerHelper( Context context, @Nullable TvInputManagerInterface tvInputManager, LegacyFlags legacyFlags) { mContext = context.getApplicationContext(); mPackageManager = context.getPackageManager(); mTvInputManager = tvInputManager; mContentRatingsManager = new ContentRatingsManager(context, tvInputManager); mParentalControlSettings = new ParentalControlSettings(context, legacyFlags); mTvInputInfoComparator = new InputComparatorInternal(this); mContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { String option = uri.getLastPathSegment(); if (option == null || !option.equals(TV_INPUT_ALLOW_3RD_PARTY_INPUTS)) { return; } boolean previousSetting = mAllow3rdPartyInputs; updateAllow3rdPartyInputs(); if (previousSetting == mAllow3rdPartyInputs) { return; } initInputMaps(); } }; } public void start() { if (!hasTvInputManager()) { // Not a TV device return; } if (mStarted) { return; } if (DEBUG) Log.d(TAG, "start"); mStarted = true; mContext.getContentResolver() .registerContentObserver( Settings.Global.getUriFor(TV_INPUT_ALLOW_3RD_PARTY_INPUTS), true, mContentObserver); updateAllow3rdPartyInputs(); mTvInputManager.registerCallback(mInternalCallback, mHandler); initInputMaps(); } public void stop() { if (!mStarted) { return; } mTvInputManager.unregisterCallback(mInternalCallback); mContext.getContentResolver().unregisterContentObserver(mContentObserver); mStarted = false; mInputStateMap.clear(); mInputMap.clear(); mTvInputLabels.clear(); mTvInputCustomLabels.clear(); mTvInputApplicationLabels.clear(); mTvInputApplicationIcons.clear(); mTvInputApplicationBanners.clear(); mInputIdToPartnerInputMap.clear(); } /** Clears the TvInput labels map. */ public void clearTvInputLabels() { mTvInputLabels.clear(); mTvInputCustomLabels.clear(); mTvInputApplicationLabels.clear(); } public List getTvInputInfos(boolean availableOnly, boolean tunerOnly) { ArrayList list = new ArrayList<>(); for (Map.Entry pair : mInputStateMap.entrySet()) { if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) { continue; } TvInputInfo input = getTvInputInfo(pair.getKey()); if (input == null || isInputBlocked(input)) { continue; } if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) { continue; } list.add(input); } Collections.sort(list, mTvInputInfoComparator); return list; } /** * Returns the default comparator for {@link TvInputInfo}. See {@link InputComparatorInternal} * for detail. */ public Comparator getDefaultTvInputInfoComparator() { return mTvInputInfoComparator; } /** * Checks if the input is from a partner. * *

It's visible for comparator test. Package private is enough for this method, but public is * necessary to workaround mockito bug. */ @VisibleForTesting public boolean isPartnerInput(TvInputInfo inputInfo) { return isSystemInput(inputInfo) && !isBundledInput(inputInfo); } /** Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. */ public boolean isSystemInput(TvInputInfo inputInfo) { return inputInfo != null && (inputInfo.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } /** Is the input one known bundled inputs not written by OEM/SOCs. */ public boolean isBundledInput(TvInputInfo inputInfo) { return inputInfo != null && CommonUtils.isInBundledPackageSet( inputInfo.getServiceInfo().applicationInfo.packageName); } /** * Returns if the given input is bundled and written by OEM/SOCs. This returns the cached * result. */ public boolean isPartnerInput(String inputId) { Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId); return (isPartnerInput != null) ? isPartnerInput : false; } /** * Is (Context.TV_INPUT_SERVICE) available. * *

This is only available on TV devices. */ public boolean hasTvInputManager() { return mTvInputManager != null; } /** Loads label of {@code info}. */ @Nullable public String loadLabel(TvInputInfo info) { String label = mTvInputLabels.get(info.getId()); if (label == null) { CharSequence labelSequence = info.loadLabel(mContext); label = labelSequence == null ? null : labelSequence.toString(); mTvInputLabels.put(info.getId(), label); } return label; } /** Loads custom label of {@code info} */ public String loadCustomLabel(TvInputInfo info) { String customLabel = mTvInputCustomLabels.get(info.getId()); if (customLabel == null) { CharSequence customLabelCharSequence = info.loadCustomLabel(mContext); if (customLabelCharSequence != null) { customLabel = customLabelCharSequence.toString(); mTvInputCustomLabels.put(info.getId(), customLabel); } } return customLabel; } /** Gets the tv input application's label. */ public CharSequence getTvInputApplicationLabel(CharSequence inputId) { return mTvInputApplicationLabels.get(inputId); } /** Stores the tv input application's label. */ public void setTvInputApplicationLabel(String inputId, CharSequence label) { mTvInputApplicationLabels.put(inputId, label); } /** Gets the tv input application's icon. */ public Drawable getTvInputApplicationIcon(String inputId) { return mTvInputApplicationIcons.get(inputId); } /** Stores the tv input application's icon. */ public void setTvInputApplicationIcon(String inputId, Drawable icon) { mTvInputApplicationIcons.put(inputId, icon); } /** Gets the tv input application's banner. */ public Drawable getTvInputApplicationBanner(String inputId) { return mTvInputApplicationBanners.get(inputId); } /** Stores the tv input application's banner. */ public void setTvInputApplicationBanner(String inputId, Drawable banner) { mTvInputApplicationBanners.put(inputId, banner); } /** Returns if TV input exists with the input id. */ public boolean hasTvInputInfo(String inputId) { SoftPreconditions.checkState( mStarted, TAG, "hasTvInputInfo() called before TvInputManagerHelper was started."); return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null; } @Nullable public TvInputInfo getTvInputInfo(String inputId) { TvInputInfoCompat inputInfo = getTvInputInfoCompat(inputId); return inputInfo == null ? null : inputInfo.getTvInputInfo(); } @Nullable public TvInputInfoCompat getTvInputInfoCompat(String inputId) { SoftPreconditions.checkState( mStarted, TAG, "getTvInputInfo() called before TvInputManagerHelper was started."); if (!mStarted) { return null; } if (inputId == null) { return null; } return mInputMap.get(inputId); } public ApplicationInfo getTvInputAppInfo(String inputId) { TvInputInfo info = getTvInputInfo(inputId); return info == null ? null : info.getServiceInfo().applicationInfo; } public int getTunerTvInputSize() { int size = 0; for (TvInputInfoCompat input : mInputMap.values()) { if (input.getType() == TvInputInfo.TYPE_TUNER) { ++size; } } return size; } /** * Returns TvInputInfo's input state. * * @param inputInfo * @return An Integer which stands for the input state {@link * TvInputManager.INPUT_STATE_DISCONNECTED} if inputInfo is null */ public int getInputState(@Nullable TvInputInfo inputInfo) { return inputInfo == null ? TvInputManager.INPUT_STATE_DISCONNECTED : getInputState(inputInfo.getId()); } public int getInputState(String inputId) { SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started"); if (!mStarted) { return TvInputManager.INPUT_STATE_DISCONNECTED; } Integer state = mInputStateMap.get(inputId); if (state == null) { Log.w(TAG, "getInputState: no such input (id=" + inputId + ")"); return TvInputManager.INPUT_STATE_DISCONNECTED; } return state; } public void addCallback(TvInputCallback callback) { mCallbacks.add(callback); } public void removeCallback(TvInputCallback callback) { mCallbacks.remove(callback); } public ParentalControlSettings getParentalControlSettings() { return mParentalControlSettings; } /** Returns a ContentRatingsManager instance for a given application context. */ public ContentRatingsManager getContentRatingsManager() { return mContentRatingsManager; } private int getInputSortKey(TvInputInfo input) { return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, Integer.MAX_VALUE); } private boolean isInputPhysicalTuner(TvInputInfo input) { String packageName = input.getServiceInfo().packageName; if (Arrays.asList(mPhysicalTunerBlockList).contains(packageName)) { return false; } if (input.createSetupIntent() == null) { return false; } else { boolean mayBeTunerInput = mPackageManager.checkPermission( PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED; if (!mayBeTunerInput) { try { ApplicationInfo ai = mPackageManager.getApplicationInfo( input.getServiceInfo().packageName, 0); if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) { return false; } } catch (PackageManager.NameNotFoundException e) { return false; } } } return true; } private boolean isBlocked(String inputId) { if (TvFeatures.USE_PARTNER_INPUT_BLOCKLIST.isEnabled(mContext)) { for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLOCKLIST) { if (inputId.contains(disabledTunerInputPrefix)) { return true; } } } if (CommonUtils.isRoboTest()) return false; if (CommonUtils.isRunningInTest()) { for (String testableInput : TESTABLE_INPUTS) { if (testableInput.equals(inputId)) { return false; } } return true; } return false; } private void initInputMaps() { mInputMap.clear(); mTvInputLabels.clear(); mTvInputCustomLabels.clear(); mTvInputApplicationLabels.clear(); mTvInputApplicationIcons.clear(); mTvInputApplicationBanners.clear(); mInputStateMap.clear(); mInputIdToPartnerInputMap.clear(); for (TvInputInfo input : mTvInputManager.getTvInputList()) { if (DEBUG) { Log.d(TAG, "Input detected " + input); } String inputId = input.getId(); if (isInputBlocked(input)) { continue; } mInputMap.put(inputId, new TvInputInfoCompat(mContext, input)); int state = mTvInputManager.getInputState(inputId); mInputStateMap.put(inputId, state); mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input)); } SoftPreconditions.checkState( mInputStateMap.size() == mInputMap.size(), TAG, "mInputStateMap not the same size as mInputMap"); } private void updateAllow3rdPartyInputs() { int setting; try { setting = Settings.Global.getInt( mContext.getContentResolver(), TV_INPUT_ALLOW_3RD_PARTY_INPUTS); } catch (SettingNotFoundException e) { mAllow3rdPartyInputs = SystemProperties.ALLOW_THIRD_PARTY_INPUTS.getValue(); return; } mAllow3rdPartyInputs = setting == 1; } private boolean isInputBlocked(TvInputInfo info) { if (!mAllow3rdPartyInputs) { if (!isSystemInput(info)) { return true; } for (String id : SYSTEM_INPUT_ID_BLOCKLIST) { if (info.getId().startsWith(id)) { return true; } } } return isBlocked(info.getId()); } /** * Default comparator for TvInputInfo. * *

It's static class that accepts {@link TvInputManagerHelper} as parameter to test. To test * comparator, we need to mock API in parent class such as {@link #isPartnerInput}, but it's * impossible for an inner class to use mocked methods. (i.e. Mockito's spy doesn't work) */ @VisibleForTesting static class InputComparatorInternal implements Comparator { private final TvInputManagerHelper mInputManager; private static final Ordering NULL_FIRST_STRING_ORDERING = Ordering.natural().nullsFirst(); public InputComparatorInternal(TvInputManagerHelper inputManager) { mInputManager = inputManager; } @Override public int compare(TvInputInfo lhs, TvInputInfo rhs) { if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) { return mInputManager.isPartnerInput(lhs) ? -1 : 1; } return NULL_FIRST_STRING_ORDERING.compare( mInputManager.loadLabel(lhs), mInputManager.loadLabel(rhs)); } } /** * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of TV * inputs. */ public static class HardwareInputComparator implements Comparator { private Map mTypePriorities = new HashMap<>(); private final TvInputManagerHelper mTvInputManagerHelper; private final Context mContext; public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) { mContext = context; mTvInputManagerHelper = tvInputManagerHelper; setupDeviceTypePriorities(); } @Override public int compare(TvInputInfo lhs, TvInputInfo rhs) { if (lhs == null) { return (rhs == null) ? 0 : 1; } if (rhs == null) { return -1; } boolean enabledL = (mTvInputManagerHelper.getInputState(lhs) != TvInputManager.INPUT_STATE_DISCONNECTED); boolean enabledR = (mTvInputManagerHelper.getInputState(rhs) != TvInputManager.INPUT_STATE_DISCONNECTED); if (enabledL != enabledR) { return enabledL ? -1 : 1; } int priorityL = getPriority(lhs); int priorityR = getPriority(rhs); if (priorityL != priorityR) { return priorityL - priorityR; } if (lhs.getType() == TvInputInfo.TYPE_TUNER && rhs.getType() == TvInputInfo.TYPE_TUNER) { boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs); boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs); if (isPhysicalL != isPhysicalR) { return isPhysicalL ? -1 : 1; } } int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs); int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs); if (sortKeyL != sortKeyR) { return sortKeyR - sortKeyL; } String parentLabelL = lhs.getParentId() != null ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId())) : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId())); String parentLabelR = rhs.getParentId() != null ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId())) : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId())); if (!TextUtils.equals(parentLabelL, parentLabelR)) { return parentLabelL.compareToIgnoreCase(parentLabelR); } return getLabel(lhs).compareToIgnoreCase(getLabel(rhs)); } private String getLabel(TvInputInfo input) { if (input == null) { return ""; } String label = mTvInputManagerHelper.loadCustomLabel(input); if (TextUtils.isEmpty(label)) { label = mTvInputManagerHelper.loadLabel(input); } return label == null ? "" : label; } private int getPriority(TvInputInfo info) { Integer priority = null; if (mTypePriorities != null) { priority = mTypePriorities.get(getTvInputTypeForPriority(info)); } if (priority != null) { return priority; } return Integer.MAX_VALUE; } private void setupDeviceTypePriorities() { mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap(); // Fill in any missing priorities in the map we got from the OEM int priority = mTypePriorities.size(); for (int type : DEFAULT_TV_INPUT_PRIORITY) { if (!mTypePriorities.containsKey(type)) { mTypePriorities.put(type, priority++); } } } private int getTvInputTypeForPriority(TvInputInfo info) { if (info.getHdmiDeviceInfo() != null) { if (info.getHdmiDeviceInfo().isCecDevice()) { switch (info.getHdmiDeviceInfo().getDeviceType()) { case HdmiDeviceInfo.DEVICE_RECORDER: return TYPE_CEC_DEVICE_RECORDER; case HdmiDeviceInfo.DEVICE_PLAYBACK: return TYPE_CEC_DEVICE_PLAYBACK; default: return TYPE_CEC_DEVICE; } } else if (info.getHdmiDeviceInfo().isMhlDevice()) { return TYPE_MHL_MOBILE; } } return info.getType(); } } }