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