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