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 TvInputManagerHelper(Context context)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 start()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 stop()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 */ clearTvInputLabels()274 public void clearTvInputLabels() { 275 mTvInputLabels.clear(); 276 mTvInputCustomLabels.clear(); 277 mTvInputApplicationLabels.clear(); 278 } 279 getTvInputInfos(boolean availableOnly, boolean tunerOnly)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 */ getDefaultTvInputInfoComparator()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 isPartnerInput(TvInputInfo inputInfo)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 */ isSystemInput(TvInputInfo inputInfo)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 */ isBundledInput(TvInputInfo inputInfo)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 */ isPartnerInput(String inputId)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 */ hasTvInputManager()347 public boolean hasTvInputManager() { 348 return mTvInputManager != null; 349 } 350 351 /** 352 * Loads label of {@code info}. 353 */ loadLabel(TvInputInfo info)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 */ loadCustomLabel(TvInputInfo info)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 */ getTvInputApplicationLabel(CharSequence inputId)381 public CharSequence getTvInputApplicationLabel(CharSequence inputId) { 382 return mTvInputApplicationLabels.get(inputId); 383 } 384 385 /** 386 * Stores the tv input application's label. 387 */ setTvInputApplicationLabel(String inputId, CharSequence label)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 */ getTvInputApplicationIcon(String inputId)395 public Drawable getTvInputApplicationIcon(String inputId) { 396 return mTvInputApplicationIcons.get(inputId); 397 } 398 399 /** 400 * Stores the tv input application's icon. 401 */ setTvInputApplicationIcon(String inputId, Drawable icon)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 */ getTvInputApplicationBanner(String inputId)409 public Drawable getTvInputApplicationBanner(String inputId) { 410 return mTvInputAppliactionBanners.get(inputId); 411 } 412 413 /** 414 * Stores the tv input application's banner. 415 */ setTvInputApplicationBanner(String inputId, Drawable banner)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 */ hasTvInputInfo(String inputId)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 getTvInputInfo(String inputId)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 getTvInputAppInfo(String inputId)441 public ApplicationInfo getTvInputAppInfo(String inputId) { 442 TvInputInfo info = getTvInputInfo(inputId); 443 return info == null ? null : info.getServiceInfo().applicationInfo; 444 } 445 getTunerTvInputSize()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 getInputState(TvInputInfo inputInfo)456 public int getInputState(TvInputInfo inputInfo) { 457 return getInputState(inputInfo.getId()); 458 } 459 getInputState(String inputId)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 addCallback(TvInputCallback callback)474 public void addCallback(TvInputCallback callback) { 475 mCallbacks.add(callback); 476 } 477 removeCallback(TvInputCallback callback)478 public void removeCallback(TvInputCallback callback) { 479 mCallbacks.remove(callback); 480 } 481 getParentalControlSettings()482 public ParentalControlSettings getParentalControlSettings() { 483 return mParentalControlSettings; 484 } 485 486 /** 487 * Returns a ContentRatingsManager instance for a given application context. 488 */ getContentRatingsManager()489 public ContentRatingsManager getContentRatingsManager() { 490 return mContentRatingsManager; 491 } 492 getInputSortKey(TvInputInfo input)493 private int getInputSortKey(TvInputInfo input) { 494 return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, 495 Integer.MAX_VALUE); 496 } 497 isInputPhysicalTuner(TvInputInfo input)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 isInBlackList(String inputId)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 InputComparatorInternal(TvInputManagerHelper inputManager)557 public InputComparatorInternal(TvInputManagerHelper inputManager) { 558 mInputManager = inputManager; 559 } 560 561 @Override compare(TvInputInfo lhs, TvInputInfo rhs)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 HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper)579 public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) { 580 mContext = context; 581 mTvInputManagerHelper = tvInputManagerHelper; 582 setupDeviceTypePriorities(); 583 } 584 585 @Override compare(TvInputInfo lhs, TvInputInfo rhs)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 getLabel(TvInputInfo input)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 getPriority(TvInputInfo info)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 setupDeviceTypePriorities()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 getTvInputTypeForPriority(TvInputInfo info)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