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