1 /* 2 * Copyright (C) 2014 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 android.media.tv; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.StringRes; 22 import android.annotation.SystemApi; 23 import android.compat.annotation.UnsupportedAppUsage; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.pm.ResolveInfo; 30 import android.content.pm.ServiceInfo; 31 import android.content.res.Resources; 32 import android.content.res.TypedArray; 33 import android.content.res.XmlResourceParser; 34 import android.graphics.drawable.Drawable; 35 import android.graphics.drawable.Icon; 36 import android.hardware.hdmi.HdmiControlManager; 37 import android.hardware.hdmi.HdmiDeviceInfo; 38 import android.hardware.hdmi.HdmiUtils; 39 import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition; 40 import android.net.Uri; 41 import android.os.Bundle; 42 import android.os.Parcel; 43 import android.os.Parcelable; 44 import android.os.UserHandle; 45 import android.provider.Settings; 46 import android.text.TextUtils; 47 import android.util.AttributeSet; 48 import android.util.Log; 49 import android.util.SparseIntArray; 50 import android.util.Xml; 51 52 import org.xmlpull.v1.XmlPullParser; 53 import org.xmlpull.v1.XmlPullParserException; 54 55 import java.io.IOException; 56 import java.io.InputStream; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.HashMap; 60 import java.util.HashSet; 61 import java.util.Locale; 62 import java.util.Map; 63 import java.util.Objects; 64 import java.util.Set; 65 66 /** 67 * This class is used to specify meta information of a TV input. 68 */ 69 public final class TvInputInfo implements Parcelable { 70 private static final boolean DEBUG = false; 71 private static final String TAG = "TvInputInfo"; 72 73 /** @hide */ 74 @Retention(RetentionPolicy.SOURCE) 75 @IntDef({TYPE_TUNER, TYPE_OTHER, TYPE_COMPOSITE, TYPE_SVIDEO, TYPE_SCART, TYPE_COMPONENT, 76 TYPE_VGA, TYPE_DVI, TYPE_HDMI, TYPE_DISPLAY_PORT}) 77 public @interface Type {} 78 79 // Should be in sync with frameworks/base/core/res/res/values/attrs.xml 80 /** 81 * TV input type: the TV input service is a tuner which provides channels. 82 */ 83 public static final int TYPE_TUNER = 0; 84 /** 85 * TV input type: a generic hardware TV input type. 86 */ 87 public static final int TYPE_OTHER = 1000; 88 /** 89 * TV input type: the TV input service represents a composite port. 90 */ 91 public static final int TYPE_COMPOSITE = 1001; 92 /** 93 * TV input type: the TV input service represents a SVIDEO port. 94 */ 95 public static final int TYPE_SVIDEO = 1002; 96 /** 97 * TV input type: the TV input service represents a SCART port. 98 */ 99 public static final int TYPE_SCART = 1003; 100 /** 101 * TV input type: the TV input service represents a component port. 102 */ 103 public static final int TYPE_COMPONENT = 1004; 104 /** 105 * TV input type: the TV input service represents a VGA port. 106 */ 107 public static final int TYPE_VGA = 1005; 108 /** 109 * TV input type: the TV input service represents a DVI port. 110 */ 111 public static final int TYPE_DVI = 1006; 112 /** 113 * TV input type: the TV input service is HDMI. (e.g. HDMI 1) 114 */ 115 public static final int TYPE_HDMI = 1007; 116 /** 117 * TV input type: the TV input service represents a display port. 118 */ 119 public static final int TYPE_DISPLAY_PORT = 1008; 120 121 /** 122 * Used as a String extra field in setup intents created by {@link #createSetupIntent()} to 123 * supply the ID of a specific TV input to set up. 124 */ 125 public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID"; 126 127 private final ResolveInfo mService; 128 129 private final String mId; 130 private final int mType; 131 private final boolean mIsHardwareInput; 132 133 // TODO: Remove mIconUri when createTvInputInfo() is removed. 134 private Uri mIconUri; 135 136 private final CharSequence mLabel; 137 private final int mLabelResId; 138 private final Icon mIcon; 139 private final Icon mIconStandby; 140 private final Icon mIconDisconnected; 141 142 // Attributes from XML meta data. 143 private final String mSetupActivity; 144 private final boolean mCanRecord; 145 private final int mTunerCount; 146 147 // Attributes specific to HDMI 148 private final HdmiDeviceInfo mHdmiDeviceInfo; 149 private final boolean mIsConnectedToHdmiSwitch; 150 @HdmiAddressRelativePosition 151 private final int mHdmiConnectionRelativePosition; 152 private final String mParentId; 153 154 private final Bundle mExtras; 155 156 /** 157 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 158 * ResolveInfo, and HdmiDeviceInfo. 159 * 160 * @param service The ResolveInfo returned from the package manager about this TV input service. 161 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. 162 * @param parentId The ID of this TV input's parent input. {@code null} if none exists. 163 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} 164 * label will be loaded. 165 * @param iconUri The {@link android.net.Uri} to load the icon image. See 166 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, 167 * the application icon of {@code service} will be loaded. 168 * @hide 169 * @deprecated Use {@link Builder} instead. 170 */ 171 @Deprecated 172 @SystemApi createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri)173 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 174 HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri) 175 throws XmlPullParserException, IOException { 176 TvInputInfo info = new TvInputInfo.Builder(context, service) 177 .setHdmiDeviceInfo(hdmiDeviceInfo) 178 .setParentId(parentId) 179 .setLabel(label) 180 .build(); 181 info.mIconUri = iconUri; 182 return info; 183 } 184 185 /** 186 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 187 * ResolveInfo, and HdmiDeviceInfo. 188 * 189 * @param service The ResolveInfo returned from the package manager about this TV input service. 190 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. 191 * @param parentId The ID of this TV input's parent input. {@code null} if none exists. 192 * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, 193 * {@code service} label will be loaded. 194 * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is 195 * {@code null}, the application icon of {@code service} will be loaded. 196 * @hide 197 * @deprecated Use {@link Builder} instead. 198 */ 199 @Deprecated 200 @SystemApi createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon)201 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 202 HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon) 203 throws XmlPullParserException, IOException { 204 return new TvInputInfo.Builder(context, service) 205 .setHdmiDeviceInfo(hdmiDeviceInfo) 206 .setParentId(parentId) 207 .setLabel(labelRes) 208 .setIcon(icon) 209 .build(); 210 } 211 212 /** 213 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 214 * ResolveInfo, and TvInputHardwareInfo. 215 * 216 * @param service The ResolveInfo returned from the package manager about this TV input service. 217 * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. 218 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} 219 * label will be loaded. 220 * @param iconUri The {@link android.net.Uri} to load the icon image. See 221 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, 222 * the application icon of {@code service} will be loaded. 223 * @hide 224 * @deprecated Use {@link Builder} instead. 225 */ 226 @Deprecated 227 @SystemApi createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, String label, Uri iconUri)228 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 229 TvInputHardwareInfo hardwareInfo, String label, Uri iconUri) 230 throws XmlPullParserException, IOException { 231 TvInputInfo info = new TvInputInfo.Builder(context, service) 232 .setTvInputHardwareInfo(hardwareInfo) 233 .setLabel(label) 234 .build(); 235 info.mIconUri = iconUri; 236 return info; 237 } 238 239 /** 240 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 241 * ResolveInfo, and TvInputHardwareInfo. 242 * 243 * @param service The ResolveInfo returned from the package manager about this TV input service. 244 * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. 245 * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, 246 * {@code service} label will be loaded. 247 * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is 248 * {@code null}, the application icon of {@code service} will be loaded. 249 * @hide 250 * @deprecated Use {@link Builder} instead. 251 */ 252 @Deprecated 253 @SystemApi createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon)254 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 255 TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon) 256 throws XmlPullParserException, IOException { 257 return new TvInputInfo.Builder(context, service) 258 .setTvInputHardwareInfo(hardwareInfo) 259 .setLabel(labelRes) 260 .setIcon(icon) 261 .build(); 262 } 263 TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId, Bundle extras)264 private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, 265 CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, 266 String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, 267 boolean isConnectedToHdmiSwitch, 268 @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId, 269 Bundle extras) { 270 mService = service; 271 mId = id; 272 mType = type; 273 mIsHardwareInput = isHardwareInput; 274 mLabel = label; 275 mLabelResId = labelResId; 276 mIcon = icon; 277 mIconStandby = iconStandby; 278 mIconDisconnected = iconDisconnected; 279 mSetupActivity = setupActivity; 280 mCanRecord = canRecord; 281 mTunerCount = tunerCount; 282 mHdmiDeviceInfo = hdmiDeviceInfo; 283 mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch; 284 mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition; 285 mParentId = parentId; 286 mExtras = extras; 287 } 288 289 /** 290 * Returns a unique ID for this TV input. The ID is generated from the package and class name 291 * implementing the TV input service. 292 */ getId()293 public String getId() { 294 return mId; 295 } 296 297 /** 298 * Returns the parent input ID. 299 * 300 * <p>A TV input may have a parent input if the TV input is actually a logical representation of 301 * a device behind the hardware port represented by the parent input. 302 * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV 303 * input. In this case, the parent input of this logical device is the HDMI port. 304 * 305 * <p>Applications may group inputs by parent input ID to provide an easier access to inputs 306 * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind 307 * the same HDMI port have the same parent ID, which is the ID representing the port. Thus 308 * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it 309 * together using this method. 310 * 311 * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is 312 * not specified. 313 */ getParentId()314 public String getParentId() { 315 return mParentId; 316 } 317 318 /** 319 * Returns the information of the service that implements this TV input. 320 */ getServiceInfo()321 public ServiceInfo getServiceInfo() { 322 return mService.serviceInfo; 323 } 324 325 /** 326 * Returns the component of the service that implements this TV input. 327 * @hide 328 */ 329 @UnsupportedAppUsage getComponent()330 public ComponentName getComponent() { 331 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); 332 } 333 334 /** 335 * Returns an intent to start the setup activity for this TV input. 336 */ createSetupIntent()337 public Intent createSetupIntent() { 338 if (!TextUtils.isEmpty(mSetupActivity)) { 339 Intent intent = new Intent(Intent.ACTION_MAIN); 340 intent.setClassName(mService.serviceInfo.packageName, mSetupActivity); 341 intent.putExtra(EXTRA_INPUT_ID, getId()); 342 return intent; 343 } 344 return null; 345 } 346 347 /** 348 * Returns an intent to start the settings activity for this TV input. 349 * 350 * @deprecated Use {@link #createSetupIntent()} instead. Settings activity is deprecated. 351 * Use setup activity instead to provide settings. 352 */ 353 @Deprecated createSettingsIntent()354 public Intent createSettingsIntent() { 355 return null; 356 } 357 358 /** 359 * Returns the type of this TV input. 360 */ 361 @Type getType()362 public int getType() { 363 return mType; 364 } 365 366 /** 367 * Returns the number of tuners this TV input has. 368 * 369 * <p>This method is valid only for inputs of type {@link #TYPE_TUNER}. For inputs of other 370 * types, it returns 0. 371 * 372 * <p>Tuners correspond to physical/logical resources that allow reception of TV signal. Having 373 * <i>N</i> tuners means that the TV input is capable of receiving <i>N</i> different channels 374 * concurrently. 375 */ getTunerCount()376 public int getTunerCount() { 377 return mTunerCount; 378 } 379 380 /** 381 * Returns {@code true} if this TV input can record TV programs, {@code false} otherwise. 382 */ canRecord()383 public boolean canRecord() { 384 return mCanRecord; 385 } 386 387 /** 388 * Returns domain-specific extras associated with this TV input. 389 */ getExtras()390 public Bundle getExtras() { 391 return mExtras; 392 } 393 394 /** 395 * Returns the HDMI device information of this TV input. 396 * @hide 397 */ 398 @SystemApi getHdmiDeviceInfo()399 public HdmiDeviceInfo getHdmiDeviceInfo() { 400 if (mType == TYPE_HDMI) { 401 return mHdmiDeviceInfo; 402 } 403 return null; 404 } 405 406 /** 407 * Returns {@code true} if this TV input is pass-though which does not have any real channels in 408 * TvProvider. {@code false} otherwise. 409 * 410 * @see TvContract#buildChannelUriForPassthroughInput(String) 411 */ isPassthroughInput()412 public boolean isPassthroughInput() { 413 return mType != TYPE_TUNER; 414 } 415 416 /** 417 * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner, 418 * HDMI1) {@code false} otherwise. 419 * @hide 420 */ 421 @SystemApi isHardwareInput()422 public boolean isHardwareInput() { 423 return mIsHardwareInput; 424 } 425 426 /** 427 * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e., 428 * the device isn't directly connected to a HDMI port. 429 * TODO(b/110094868): add @Deprecated for Q 430 * @hide 431 */ 432 @SystemApi isConnectedToHdmiSwitch()433 public boolean isConnectedToHdmiSwitch() { 434 return mIsConnectedToHdmiSwitch; 435 } 436 437 /** 438 * Returns the relative position of this HDMI input. 439 * TODO(b/110094868): unhide for Q 440 * @hide 441 */ 442 @HdmiAddressRelativePosition getHdmiConnectionRelativePosition()443 public int getHdmiConnectionRelativePosition() { 444 return mHdmiConnectionRelativePosition; 445 } 446 447 /** 448 * Checks if this TV input is marked hidden by the user in the settings. 449 * 450 * @param context Supplies a {@link Context} used to check if this TV input is hidden. 451 * @return {@code true} if the user marked this TV input hidden in settings. {@code false} 452 * otherwise. 453 */ isHidden(Context context)454 public boolean isHidden(Context context) { 455 return TvInputSettings.isHidden(context, mId, UserHandle.myUserId()); 456 } 457 458 /** 459 * Loads the user-displayed label for this TV input. 460 * 461 * @param context Supplies a {@link Context} used to load the label. 462 * @return a CharSequence containing the TV input's label. If the TV input does not have 463 * a label, its name is returned. 464 */ loadLabel(@onNull Context context)465 public CharSequence loadLabel(@NonNull Context context) { 466 if (mLabelResId != 0) { 467 return context.getPackageManager().getText(mService.serviceInfo.packageName, 468 mLabelResId, null); 469 } else if (!TextUtils.isEmpty(mLabel)) { 470 return mLabel; 471 } 472 return mService.loadLabel(context.getPackageManager()); 473 } 474 475 /** 476 * Loads the custom label set by user in settings. 477 * 478 * @param context Supplies a {@link Context} used to load the custom label. 479 * @return a CharSequence containing the TV input's custom label. {@code null} if there is no 480 * custom label. 481 */ loadCustomLabel(Context context)482 public CharSequence loadCustomLabel(Context context) { 483 return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId()); 484 } 485 486 /** 487 * Loads the user-displayed icon for this TV input. 488 * 489 * @param context Supplies a {@link Context} used to load the icon. 490 * @return a Drawable containing the TV input's icon. If the TV input does not have an icon, 491 * application's icon is returned. If it's unavailable too, {@code null} is returned. 492 */ loadIcon(@onNull Context context)493 public Drawable loadIcon(@NonNull Context context) { 494 if (mIcon != null) { 495 return mIcon.loadDrawable(context); 496 } else if (mIconUri != null) { 497 try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) { 498 Drawable drawable = Drawable.createFromStream(is, null); 499 if (drawable != null) { 500 return drawable; 501 } 502 } catch (IOException e) { 503 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e); 504 // Falls back. 505 } 506 } 507 return loadServiceIcon(context); 508 } 509 510 /** 511 * Loads the user-displayed icon for this TV input per input state. 512 * 513 * @param context Supplies a {@link Context} used to load the icon. 514 * @param state The input state. Should be one of the followings. 515 * {@link TvInputManager#INPUT_STATE_CONNECTED}, 516 * {@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} and 517 * {@link TvInputManager#INPUT_STATE_DISCONNECTED}. 518 * @return a Drawable containing the TV input's icon for the given state or {@code null} if such 519 * an icon is not defined. 520 * @hide 521 */ 522 @SystemApi loadIcon(@onNull Context context, int state)523 public Drawable loadIcon(@NonNull Context context, int state) { 524 if (state == TvInputManager.INPUT_STATE_CONNECTED) { 525 return loadIcon(context); 526 } else if (state == TvInputManager.INPUT_STATE_CONNECTED_STANDBY) { 527 if (mIconStandby != null) { 528 return mIconStandby.loadDrawable(context); 529 } 530 } else if (state == TvInputManager.INPUT_STATE_DISCONNECTED) { 531 if (mIconDisconnected != null) { 532 return mIconDisconnected.loadDrawable(context); 533 } 534 } else { 535 throw new IllegalArgumentException("Unknown state: " + state); 536 } 537 return null; 538 } 539 540 @Override describeContents()541 public int describeContents() { 542 return 0; 543 } 544 545 @Override hashCode()546 public int hashCode() { 547 return mId.hashCode(); 548 } 549 550 @Override equals(Object o)551 public boolean equals(Object o) { 552 if (o == this) { 553 return true; 554 } 555 556 if (!(o instanceof TvInputInfo)) { 557 return false; 558 } 559 560 TvInputInfo obj = (TvInputInfo) o; 561 return Objects.equals(mService, obj.mService) 562 && TextUtils.equals(mId, obj.mId) 563 && mType == obj.mType 564 && mIsHardwareInput == obj.mIsHardwareInput 565 && TextUtils.equals(mLabel, obj.mLabel) 566 && Objects.equals(mIconUri, obj.mIconUri) 567 && mLabelResId == obj.mLabelResId 568 && Objects.equals(mIcon, obj.mIcon) 569 && Objects.equals(mIconStandby, obj.mIconStandby) 570 && Objects.equals(mIconDisconnected, obj.mIconDisconnected) 571 && TextUtils.equals(mSetupActivity, obj.mSetupActivity) 572 && mCanRecord == obj.mCanRecord 573 && mTunerCount == obj.mTunerCount 574 && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo) 575 && mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch 576 && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition 577 && TextUtils.equals(mParentId, obj.mParentId) 578 && Objects.equals(mExtras, obj.mExtras); 579 } 580 581 @Override toString()582 public String toString() { 583 return "TvInputInfo{id=" + mId 584 + ", pkg=" + mService.serviceInfo.packageName 585 + ", service=" + mService.serviceInfo.name + "}"; 586 } 587 588 /** 589 * Used to package this object into a {@link Parcel}. 590 * 591 * @param dest The {@link Parcel} to be written. 592 * @param flags The flags used for parceling. 593 */ 594 @Override writeToParcel(@onNull Parcel dest, int flags)595 public void writeToParcel(@NonNull Parcel dest, int flags) { 596 mService.writeToParcel(dest, flags); 597 dest.writeString(mId); 598 dest.writeInt(mType); 599 dest.writeByte(mIsHardwareInput ? (byte) 1 : 0); 600 TextUtils.writeToParcel(mLabel, dest, flags); 601 dest.writeParcelable(mIconUri, flags); 602 dest.writeInt(mLabelResId); 603 dest.writeParcelable(mIcon, flags); 604 dest.writeParcelable(mIconStandby, flags); 605 dest.writeParcelable(mIconDisconnected, flags); 606 dest.writeString(mSetupActivity); 607 dest.writeByte(mCanRecord ? (byte) 1 : 0); 608 dest.writeInt(mTunerCount); 609 dest.writeParcelable(mHdmiDeviceInfo, flags); 610 dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); 611 dest.writeInt(mHdmiConnectionRelativePosition); 612 dest.writeString(mParentId); 613 dest.writeBundle(mExtras); 614 } 615 loadServiceIcon(Context context)616 private Drawable loadServiceIcon(Context context) { 617 if (mService.serviceInfo.icon == 0 618 && mService.serviceInfo.applicationInfo.icon == 0) { 619 return null; 620 } 621 return mService.serviceInfo.loadIcon(context.getPackageManager()); 622 } 623 624 public static final @android.annotation.NonNull Parcelable.Creator<TvInputInfo> CREATOR = 625 new Parcelable.Creator<TvInputInfo>() { 626 @Override 627 public TvInputInfo createFromParcel(Parcel in) { 628 return new TvInputInfo(in); 629 } 630 631 @Override 632 public TvInputInfo[] newArray(int size) { 633 return new TvInputInfo[size]; 634 } 635 }; 636 TvInputInfo(Parcel in)637 private TvInputInfo(Parcel in) { 638 mService = ResolveInfo.CREATOR.createFromParcel(in); 639 mId = in.readString(); 640 mType = in.readInt(); 641 mIsHardwareInput = in.readByte() == 1; 642 mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 643 mIconUri = in.readParcelable(null); 644 mLabelResId = in.readInt(); 645 mIcon = in.readParcelable(null); 646 mIconStandby = in.readParcelable(null); 647 mIconDisconnected = in.readParcelable(null); 648 mSetupActivity = in.readString(); 649 mCanRecord = in.readByte() == 1; 650 mTunerCount = in.readInt(); 651 mHdmiDeviceInfo = in.readParcelable(null); 652 mIsConnectedToHdmiSwitch = in.readByte() == 1; 653 mHdmiConnectionRelativePosition = in.readInt(); 654 mParentId = in.readString(); 655 mExtras = in.readBundle(); 656 } 657 658 /** 659 * A convenience builder for creating {@link TvInputInfo} objects. 660 */ 661 public static final class Builder { 662 private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4; 663 private static final int LENGTH_HDMI_DEVICE_ID = 2; 664 665 private static final String XML_START_TAG_NAME = "tv-input"; 666 private static final String DELIMITER_INFO_IN_ID = "/"; 667 private static final String PREFIX_HDMI_DEVICE = "HDMI"; 668 private static final String PREFIX_HARDWARE_DEVICE = "HW"; 669 670 private static final SparseIntArray sHardwareTypeToTvInputType = new SparseIntArray(); 671 static { sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, TYPE_OTHER)672 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, 673 TYPE_OTHER); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER)674 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, TYPE_COMPOSITE)675 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, 676 TYPE_COMPOSITE); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO)677 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART)678 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, TYPE_COMPONENT)679 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, 680 TYPE_COMPONENT); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA)681 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI)682 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI)683 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI); sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT, TYPE_DISPLAY_PORT)684 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT, 685 TYPE_DISPLAY_PORT); 686 } 687 688 private final Context mContext; 689 private final ResolveInfo mResolveInfo; 690 private CharSequence mLabel; 691 private int mLabelResId; 692 private Icon mIcon; 693 private Icon mIconStandby; 694 private Icon mIconDisconnected; 695 private String mSetupActivity; 696 private Boolean mCanRecord; 697 private Integer mTunerCount; 698 private TvInputHardwareInfo mTvInputHardwareInfo; 699 private HdmiDeviceInfo mHdmiDeviceInfo; 700 private String mParentId; 701 private Bundle mExtras; 702 703 /** 704 * Constructs a new builder for {@link TvInputInfo}. 705 * 706 * @param context A Context of the application package implementing this class. 707 * @param component The name of the application component to be used for the 708 * {@link TvInputService}. 709 */ Builder(Context context, ComponentName component)710 public Builder(Context context, ComponentName component) { 711 if (context == null) { 712 throw new IllegalArgumentException("context cannot be null."); 713 } 714 Intent intent = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component); 715 mResolveInfo = context.getPackageManager().resolveService(intent, 716 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 717 if (mResolveInfo == null) { 718 throw new IllegalArgumentException("Invalid component. Can't find the service."); 719 } 720 mContext = context; 721 } 722 723 /** 724 * Constructs a new builder for {@link TvInputInfo}. 725 * 726 * @param resolveInfo The ResolveInfo returned from the package manager about this TV input 727 * service. 728 * @hide 729 */ Builder(Context context, ResolveInfo resolveInfo)730 public Builder(Context context, ResolveInfo resolveInfo) { 731 if (context == null) { 732 throw new IllegalArgumentException("context cannot be null"); 733 } 734 if (resolveInfo == null) { 735 throw new IllegalArgumentException("resolveInfo cannot be null"); 736 } 737 mContext = context; 738 mResolveInfo = resolveInfo; 739 } 740 741 /** 742 * Sets the icon. 743 * 744 * @param icon The icon that represents this TV input. 745 * @return This Builder object to allow for chaining of calls to builder methods. 746 * @hide 747 */ 748 @SystemApi setIcon(Icon icon)749 public Builder setIcon(Icon icon) { 750 this.mIcon = icon; 751 return this; 752 } 753 754 /** 755 * Sets the icon for a given input state. 756 * 757 * @param icon The icon that represents this TV input for the given state. 758 * @param state The input state. Should be one of the followings. 759 * {@link TvInputManager#INPUT_STATE_CONNECTED}, 760 * {@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} and 761 * {@link TvInputManager#INPUT_STATE_DISCONNECTED}. 762 * @return This Builder object to allow for chaining of calls to builder methods. 763 * @hide 764 */ 765 @SystemApi setIcon(Icon icon, int state)766 public Builder setIcon(Icon icon, int state) { 767 if (state == TvInputManager.INPUT_STATE_CONNECTED) { 768 this.mIcon = icon; 769 } else if (state == TvInputManager.INPUT_STATE_CONNECTED_STANDBY) { 770 this.mIconStandby = icon; 771 } else if (state == TvInputManager.INPUT_STATE_DISCONNECTED) { 772 this.mIconDisconnected = icon; 773 } else { 774 throw new IllegalArgumentException("Unknown state: " + state); 775 } 776 return this; 777 } 778 779 /** 780 * Sets the label. 781 * 782 * @param label The text to be used as label. 783 * @return This Builder object to allow for chaining of calls to builder methods. 784 * @hide 785 */ 786 @SystemApi setLabel(CharSequence label)787 public Builder setLabel(CharSequence label) { 788 if (mLabelResId != 0) { 789 throw new IllegalStateException("Resource ID for label is already set."); 790 } 791 this.mLabel = label; 792 return this; 793 } 794 795 /** 796 * Sets the label. 797 * 798 * @param resId The resource ID of the text to use. 799 * @return This Builder object to allow for chaining of calls to builder methods. 800 * @hide 801 */ 802 @SystemApi setLabel(@tringRes int resId)803 public Builder setLabel(@StringRes int resId) { 804 if (mLabel != null) { 805 throw new IllegalStateException("Label text is already set."); 806 } 807 this.mLabelResId = resId; 808 return this; 809 } 810 811 /** 812 * Sets the HdmiDeviceInfo. 813 * 814 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. 815 * @return This Builder object to allow for chaining of calls to builder methods. 816 * @hide 817 */ 818 @SystemApi setHdmiDeviceInfo(HdmiDeviceInfo hdmiDeviceInfo)819 public Builder setHdmiDeviceInfo(HdmiDeviceInfo hdmiDeviceInfo) { 820 if (mTvInputHardwareInfo != null) { 821 Log.w(TAG, "TvInputHardwareInfo will not be used to build this TvInputInfo"); 822 mTvInputHardwareInfo = null; 823 } 824 this.mHdmiDeviceInfo = hdmiDeviceInfo; 825 return this; 826 } 827 828 /** 829 * Sets the parent ID. 830 * 831 * @param parentId The parent ID. 832 * @return This Builder object to allow for chaining of calls to builder methods. 833 * @hide 834 */ 835 @SystemApi setParentId(String parentId)836 public Builder setParentId(String parentId) { 837 this.mParentId = parentId; 838 return this; 839 } 840 841 /** 842 * Sets the TvInputHardwareInfo. 843 * 844 * @param tvInputHardwareInfo 845 * @return This Builder object to allow for chaining of calls to builder methods. 846 * @hide 847 */ 848 @SystemApi setTvInputHardwareInfo(TvInputHardwareInfo tvInputHardwareInfo)849 public Builder setTvInputHardwareInfo(TvInputHardwareInfo tvInputHardwareInfo) { 850 if (mHdmiDeviceInfo != null) { 851 Log.w(TAG, "mHdmiDeviceInfo will not be used to build this TvInputInfo"); 852 mHdmiDeviceInfo = null; 853 } 854 this.mTvInputHardwareInfo = tvInputHardwareInfo; 855 return this; 856 } 857 858 /** 859 * Sets the tuner count. Valid only for {@link #TYPE_TUNER}. 860 * 861 * @param tunerCount The number of tuners this TV input has. 862 * @return This Builder object to allow for chaining of calls to builder methods. 863 */ setTunerCount(int tunerCount)864 public Builder setTunerCount(int tunerCount) { 865 this.mTunerCount = tunerCount; 866 return this; 867 } 868 869 /** 870 * Sets whether this TV input can record TV programs or not. 871 * 872 * @param canRecord Whether this TV input can record TV programs. 873 * @return This Builder object to allow for chaining of calls to builder methods. 874 */ setCanRecord(boolean canRecord)875 public Builder setCanRecord(boolean canRecord) { 876 this.mCanRecord = canRecord; 877 return this; 878 } 879 880 /** 881 * Sets domain-specific extras associated with this TV input. 882 * 883 * @param extras Domain-specific extras associated with this TV input. Keys <em>must</em> be 884 * a scoped name, i.e. prefixed with a package name you own, so that different 885 * developers will not create conflicting keys. 886 * @return This Builder object to allow for chaining of calls to builder methods. 887 */ setExtras(Bundle extras)888 public Builder setExtras(Bundle extras) { 889 this.mExtras = extras; 890 return this; 891 } 892 893 /** 894 * Creates a {@link TvInputInfo} instance with the specified fields. Most of the information 895 * is obtained by parsing the AndroidManifest and {@link TvInputService#SERVICE_META_DATA} 896 * for the {@link TvInputService} this TV input implements. 897 * 898 * @return TvInputInfo containing information about this TV input. 899 */ build()900 public TvInputInfo build() { 901 ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName, 902 mResolveInfo.serviceInfo.name); 903 String id; 904 int type; 905 boolean isHardwareInput = false; 906 boolean isConnectedToHdmiSwitch = false; 907 @HdmiAddressRelativePosition 908 int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; 909 910 if (mHdmiDeviceInfo != null) { 911 id = generateInputId(componentName, mHdmiDeviceInfo); 912 type = TYPE_HDMI; 913 isHardwareInput = true; 914 hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); 915 isConnectedToHdmiSwitch = 916 hdmiConnectionRelativePosition 917 != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; 918 } else if (mTvInputHardwareInfo != null) { 919 id = generateInputId(componentName, mTvInputHardwareInfo); 920 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); 921 isHardwareInput = true; 922 } else { 923 id = generateInputId(componentName); 924 type = TYPE_TUNER; 925 } 926 parseServiceMetadata(type); 927 return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, 928 mIcon, mIconStandby, mIconDisconnected, mSetupActivity, 929 mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount, 930 mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition, 931 mParentId, mExtras); 932 } 933 generateInputId(ComponentName name)934 private static String generateInputId(ComponentName name) { 935 return name.flattenToShortString(); 936 } 937 generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo)938 private static String generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo) { 939 // Example of the format : "/HDMI%04X%02X" 940 String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE 941 + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X" 942 + "%0" + LENGTH_HDMI_DEVICE_ID + "X"; 943 return name.flattenToShortString() + String.format(Locale.ENGLISH, format, 944 hdmiDeviceInfo.getPhysicalAddress(), hdmiDeviceInfo.getId()); 945 } 946 generateInputId(ComponentName name, TvInputHardwareInfo tvInputHardwareInfo)947 private static String generateInputId(ComponentName name, 948 TvInputHardwareInfo tvInputHardwareInfo) { 949 return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE 950 + tvInputHardwareInfo.getDeviceId(); 951 } 952 getRelativePosition(Context context, HdmiDeviceInfo info)953 private static int getRelativePosition(Context context, HdmiDeviceInfo info) { 954 HdmiControlManager hcm = 955 (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE); 956 if (hcm == null) { 957 return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; 958 } 959 return HdmiUtils.getHdmiAddressRelativePosition( 960 info.getPhysicalAddress(), hcm.getPhysicalAddress()); 961 } 962 parseServiceMetadata(int inputType)963 private void parseServiceMetadata(int inputType) { 964 ServiceInfo si = mResolveInfo.serviceInfo; 965 PackageManager pm = mContext.getPackageManager(); 966 try (XmlResourceParser parser = 967 si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA)) { 968 if (parser == null) { 969 throw new IllegalStateException("No " + TvInputService.SERVICE_META_DATA 970 + " meta-data found for " + si.name); 971 } 972 973 Resources res = pm.getResourcesForApplication(si.applicationInfo); 974 AttributeSet attrs = Xml.asAttributeSet(parser); 975 976 int type; 977 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 978 && type != XmlPullParser.START_TAG) { 979 } 980 981 String nodeName = parser.getName(); 982 if (!XML_START_TAG_NAME.equals(nodeName)) { 983 throw new IllegalStateException("Meta-data does not start with " 984 + XML_START_TAG_NAME + " tag for " + si.name); 985 } 986 987 TypedArray sa = res.obtainAttributes(attrs, 988 com.android.internal.R.styleable.TvInputService); 989 mSetupActivity = sa.getString( 990 com.android.internal.R.styleable.TvInputService_setupActivity); 991 if (mCanRecord == null) { 992 mCanRecord = sa.getBoolean( 993 com.android.internal.R.styleable.TvInputService_canRecord, false); 994 } 995 if (mTunerCount == null && inputType == TYPE_TUNER) { 996 mTunerCount = sa.getInt( 997 com.android.internal.R.styleable.TvInputService_tunerCount, 1); 998 } 999 sa.recycle(); 1000 } catch (IOException | XmlPullParserException e) { 1001 throw new IllegalStateException("Failed reading meta-data for " + si.packageName, e); 1002 } catch (NameNotFoundException e) { 1003 throw new IllegalStateException("No resources found for " + si.packageName, e); 1004 } 1005 } 1006 } 1007 1008 /** 1009 * Utility class for putting and getting settings for TV input. 1010 * 1011 * @hide 1012 */ 1013 @SystemApi 1014 public static final class TvInputSettings { 1015 private static final String TV_INPUT_SEPARATOR = ":"; 1016 private static final String CUSTOM_NAME_SEPARATOR = ","; 1017 TvInputSettings()1018 private TvInputSettings() { } 1019 isHidden(Context context, String inputId, int userId)1020 private static boolean isHidden(Context context, String inputId, int userId) { 1021 return getHiddenTvInputIds(context, userId).contains(inputId); 1022 } 1023 getCustomLabel(Context context, String inputId, int userId)1024 private static String getCustomLabel(Context context, String inputId, int userId) { 1025 return getCustomLabels(context, userId).get(inputId); 1026 } 1027 1028 /** 1029 * Returns a set of TV input IDs which are marked as hidden by user in the settings. 1030 * 1031 * @param context The application context 1032 * @param userId The user ID for the stored hidden input set 1033 * @hide 1034 */ 1035 @SystemApi getHiddenTvInputIds(Context context, int userId)1036 public static Set<String> getHiddenTvInputIds(Context context, int userId) { 1037 String hiddenIdsString = Settings.Secure.getStringForUser( 1038 context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId); 1039 Set<String> set = new HashSet<>(); 1040 if (TextUtils.isEmpty(hiddenIdsString)) { 1041 return set; 1042 } 1043 String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR); 1044 for (String id : ids) { 1045 set.add(Uri.decode(id)); 1046 } 1047 return set; 1048 } 1049 1050 /** 1051 * Returns a map of TV input ID/custom label pairs set by the user in the settings. 1052 * 1053 * @param context The application context 1054 * @param userId The user ID for the stored hidden input map 1055 * @hide 1056 */ 1057 @SystemApi getCustomLabels(Context context, int userId)1058 public static Map<String, String> getCustomLabels(Context context, int userId) { 1059 String labelsString = Settings.Secure.getStringForUser( 1060 context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId); 1061 Map<String, String> map = new HashMap<>(); 1062 if (TextUtils.isEmpty(labelsString)) { 1063 return map; 1064 } 1065 String[] pairs = labelsString.split(TV_INPUT_SEPARATOR); 1066 for (String pairString : pairs) { 1067 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR); 1068 map.put(Uri.decode(pair[0]), Uri.decode(pair[1])); 1069 } 1070 return map; 1071 } 1072 1073 /** 1074 * Stores a set of TV input IDs which are marked as hidden by user. This is expected to 1075 * be called from the settings app. 1076 * 1077 * @param context The application context 1078 * @param hiddenInputIds A set including all the hidden TV input IDs 1079 * @param userId The user ID for the stored hidden input set 1080 * @hide 1081 */ 1082 @SystemApi putHiddenTvInputs(Context context, Set<String> hiddenInputIds, int userId)1083 public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds, 1084 int userId) { 1085 StringBuilder builder = new StringBuilder(); 1086 boolean firstItem = true; 1087 for (String inputId : hiddenInputIds) { 1088 ensureValidField(inputId); 1089 if (firstItem) { 1090 firstItem = false; 1091 } else { 1092 builder.append(TV_INPUT_SEPARATOR); 1093 } 1094 builder.append(Uri.encode(inputId)); 1095 } 1096 Settings.Secure.putStringForUser(context.getContentResolver(), 1097 Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId); 1098 1099 // Notify of the TvInputInfo changes. 1100 TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 1101 for (String inputId : hiddenInputIds) { 1102 TvInputInfo info = tm.getTvInputInfo(inputId); 1103 if (info != null) { 1104 tm.updateTvInputInfo(info); 1105 } 1106 } 1107 } 1108 1109 /** 1110 * Stores a map of TV input ID/custom label set by user. This is expected to be 1111 * called from the settings app. 1112 * 1113 * @param context The application context. 1114 * @param customLabels A map of TV input ID/custom label pairs 1115 * @param userId The user ID for the stored hidden input map 1116 * @hide 1117 */ 1118 @SystemApi putCustomLabels(Context context, Map<String, String> customLabels, int userId)1119 public static void putCustomLabels(Context context, 1120 Map<String, String> customLabels, int userId) { 1121 StringBuilder builder = new StringBuilder(); 1122 boolean firstItem = true; 1123 for (Map.Entry<String, String> entry: customLabels.entrySet()) { 1124 ensureValidField(entry.getKey()); 1125 ensureValidField(entry.getValue()); 1126 if (firstItem) { 1127 firstItem = false; 1128 } else { 1129 builder.append(TV_INPUT_SEPARATOR); 1130 } 1131 builder.append(Uri.encode(entry.getKey())); 1132 builder.append(CUSTOM_NAME_SEPARATOR); 1133 builder.append(Uri.encode(entry.getValue())); 1134 } 1135 Settings.Secure.putStringForUser(context.getContentResolver(), 1136 Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId); 1137 1138 // Notify of the TvInputInfo changes. 1139 TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 1140 for (String inputId : customLabels.keySet()) { 1141 TvInputInfo info = tm.getTvInputInfo(inputId); 1142 if (info != null) { 1143 tm.updateTvInputInfo(info); 1144 } 1145 } 1146 } 1147 ensureValidField(String value)1148 private static void ensureValidField(String value) { 1149 if (TextUtils.isEmpty(value)) { 1150 throw new IllegalArgumentException(value + " should not empty "); 1151 } 1152 } 1153 } 1154 } 1155