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