diff options
Diffstat (limited to 'src/com/android/tv/util/TvInputManagerHelper.java')
-rw-r--r-- | src/com/android/tv/util/TvInputManagerHelper.java | 358 |
1 files changed, 343 insertions, 15 deletions
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index 121f56ed..730a985b 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -18,20 +18,26 @@ package com.android.tv.util; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; import android.os.Handler; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import com.android.tv.Features; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.TvCommonUtils; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -42,14 +48,64 @@ import java.util.Map; public class TvInputManagerHelper { private static final String TAG = "TvInputManagerHelper"; private static final boolean DEBUG = false; + + /** + * 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 [] mPhysicalTunerBlackList = { + }; + private static final String META_LABEL_SORT_KEY = "input_sort_key"; + + /** + * The default tv input priority to show. + */ + private static final ArrayList<Integer> 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_BLACKLIST = { }; + private static final String[] TESTABLE_INPUTS = { + "com.android.tv.testinput/.TestTvInputService" + }; + private final Context mContext; + private final PackageManager mPackageManager; private final TvInputManager mTvInputManager; private final Map<String, Integer> mInputStateMap = new HashMap<>(); private final Map<String, TvInputInfo> mInputMap = new HashMap<>(); + private final Map<String, String> mTvInputLabels = new ArrayMap<>(); + private final Map<String, String> mTvInputCustomLabels = new ArrayMap<>(); private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>(); + + private final Map<String, CharSequence> mTvInputApplicationLabels = new ArrayMap<>(); + private final Map<String, Drawable> mTvInputApplicationIcons = new ArrayMap<>(); + private final Map<String, Drawable> mTvInputAppliactionBanners = new ArrayMap<>(); + private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override public void onInputStateChanged(String inputId, int state) { @@ -72,6 +128,11 @@ public class TvInputManagerHelper { TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); if (info != null) { mInputMap.put(inputId, info); + mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = info.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + } mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId)); mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info)); } @@ -85,6 +146,11 @@ public class TvInputManagerHelper { 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); + mTvInputAppliactionBanners.remove(inputId); mInputStateMap.remove(inputId); mInputIdToPartnerInputMap.remove(inputId); mContentRatingsManager.update(); @@ -103,6 +169,14 @@ public class TvInputManagerHelper { } TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); mInputMap.put(inputId, 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); + mTvInputAppliactionBanners.remove(inputId); for (TvInputCallback callback : mCallbacks) { callback.onInputUpdated(inputId); } @@ -114,6 +188,11 @@ public class TvInputManagerHelper { public void onTvInputInfoUpdated(TvInputInfo inputInfo) { if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo); mInputMap.put(inputInfo.getId(), 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); } @@ -131,13 +210,18 @@ public class TvInputManagerHelper { public TvInputManagerHelper(Context context) { mContext = context.getApplicationContext(); + mPackageManager = context.getPackageManager(); mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); mContentRatingsManager = new ContentRatingsManager(context); mParentalControlSettings = new ParentalControlSettings(context); - mTvInputInfoComparator = new TvInputInfoComparator(this); + mTvInputInfoComparator = new InputComparatorInternal(this); } public void start() { + if (!hasTvInputManager()) { + // Not a TV device + return; + } if (mStarted) { return; } @@ -145,6 +229,11 @@ public class TvInputManagerHelper { mStarted = true; mTvInputManager.registerCallback(mInternalCallback, mHandler); mInputMap.clear(); + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + mTvInputApplicationIcons.clear(); + mTvInputAppliactionBanners.clear(); mInputStateMap.clear(); mInputIdToPartnerInputMap.clear(); for (TvInputInfo input : mTvInputManager.getTvInputList()) { @@ -171,9 +260,23 @@ public class TvInputManagerHelper { mStarted = false; mInputStateMap.clear(); mInputMap.clear(); + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + mTvInputApplicationIcons.clear(); + mTvInputAppliactionBanners.clear();; mInputIdToPartnerInputMap.clear(); } + /** + * Clears the TvInput labels map. + */ + public void clearTvInputLabels() { + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + } + public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) { ArrayList<TvInputInfo> list = new ArrayList<>(); for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) { @@ -192,7 +295,7 @@ public class TvInputManagerHelper { /** * Returns the default comparator for {@link TvInputInfo}. - * See {@link TvInputInfoComparator} for detail. + * See {@link InputComparatorInternal} for detail. */ public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() { return mTvInputInfoComparator; @@ -237,15 +340,81 @@ public class TvInputManagerHelper { } /** - * Loads label of {@code info}. + * Is (Context.TV_INPUT_SERVICE) available. * - * It's visible for comparator test to mock TvInputInfo. - * Package private is enough for this method, but public is necessary to workaround mockito - * bug. + * <p>This is only available on TV devices. + */ + public boolean hasTvInputManager() { + return mTvInputManager != null; + } + + /** + * Loads label of {@code info}. */ - @VisibleForTesting public String loadLabel(TvInputInfo info) { - return info.loadLabel(mContext).toString(); + String label = mTvInputLabels.get(info.getId()); + if (label == null) { + label = info.loadLabel(mContext).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 mTvInputAppliactionBanners.get(inputId); + } + + /** + * Stores the tv input application's banner. + */ + public void setTvInputApplicationBanner(String inputId, Drawable banner) { + mTvInputAppliactionBanners.put(inputId, banner); } /** @@ -321,14 +490,54 @@ public class TvInputManagerHelper { return mContentRatingsManager; } - private boolean isInBlackList(String inputId) { - if (!Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { + 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(mPhysicalTunerBlackList).contains(packageName)) { return false; } - for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) { - if (inputId.contains(disabledTunerInputPrefix)) { - return true; + + 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 isInBlackList(String inputId) { + if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { + for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) { + if (inputId.contains(disabledTunerInputPrefix)) { + return true; + } + } + } + if (TvCommonUtils.isRunningInTest()) { + for (String testableInput : TESTABLE_INPUTS) { + if (testableInput.equals(inputId)) { + return false; + } } + return true; } return false; } @@ -342,10 +551,10 @@ public class TvInputManagerHelper { * (i.e. Mockito's spy doesn't work) */ @VisibleForTesting - static class TvInputInfoComparator implements Comparator<TvInputInfo> { + static class InputComparatorInternal implements Comparator<TvInputInfo> { private final TvInputManagerHelper mInputManager; - public TvInputInfoComparator(TvInputManagerHelper inputManager) { + public InputComparatorInternal(TvInputManagerHelper inputManager) { mInputManager = inputManager; } @@ -357,4 +566,123 @@ public class TvInputManagerHelper { return mInputManager.loadLabel(lhs).compareTo(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<TvInputInfo> { + private Map<Integer, Integer> 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; + } + + 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(); + } + } } |