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