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.NonNull; 20 import android.annotation.SystemApi; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.ResolveInfo; 27 import android.content.pm.ServiceInfo; 28 import android.content.res.Resources; 29 import android.content.res.TypedArray; 30 import android.content.res.XmlResourceParser; 31 import android.graphics.drawable.Drawable; 32 import android.graphics.drawable.Icon; 33 import android.hardware.hdmi.HdmiDeviceInfo; 34 import android.net.Uri; 35 import android.os.Parcel; 36 import android.os.Parcelable; 37 import android.os.UserHandle; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.AttributeSet; 41 import android.util.Log; 42 import android.util.SparseIntArray; 43 import android.util.Xml; 44 45 import org.xmlpull.v1.XmlPullParser; 46 import org.xmlpull.v1.XmlPullParserException; 47 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.Locale; 53 import java.util.Map; 54 import java.util.Set; 55 56 /** 57 * This class is used to specify meta information of a TV input. 58 */ 59 public final class TvInputInfo implements Parcelable { 60 private static final boolean DEBUG = false; 61 private static final String TAG = "TvInputInfo"; 62 63 // Should be in sync with frameworks/base/core/res/res/values/attrs.xml 64 /** 65 * TV input type: the TV input service is a tuner which provides channels. 66 */ 67 public static final int TYPE_TUNER = 0; 68 /** 69 * TV input type: a generic hardware TV input type. 70 */ 71 public static final int TYPE_OTHER = 1000; 72 /** 73 * TV input type: the TV input service represents a composite port. 74 */ 75 public static final int TYPE_COMPOSITE = 1001; 76 /** 77 * TV input type: the TV input service represents a SVIDEO port. 78 */ 79 public static final int TYPE_SVIDEO = 1002; 80 /** 81 * TV input type: the TV input service represents a SCART port. 82 */ 83 public static final int TYPE_SCART = 1003; 84 /** 85 * TV input type: the TV input service represents a component port. 86 */ 87 public static final int TYPE_COMPONENT = 1004; 88 /** 89 * TV input type: the TV input service represents a VGA port. 90 */ 91 public static final int TYPE_VGA = 1005; 92 /** 93 * TV input type: the TV input service represents a DVI port. 94 */ 95 public static final int TYPE_DVI = 1006; 96 /** 97 * TV input type: the TV input service is HDMI. (e.g. HDMI 1) 98 */ 99 public static final int TYPE_HDMI = 1007; 100 /** 101 * TV input type: the TV input service represents a display port. 102 */ 103 public static final int TYPE_DISPLAY_PORT = 1008; 104 105 /** 106 * The ID of the TV input to provide to the setup activity and settings activity. 107 */ 108 public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID"; 109 110 private static final SparseIntArray sHardwareTypeToTvInputType = new SparseIntArray(); 111 112 private static final String XML_START_TAG_NAME = "tv-input"; 113 private static final String DELIMITER_INFO_IN_ID = "/"; 114 private static final String PREFIX_HDMI_DEVICE = "HDMI"; 115 private static final String PREFIX_HARDWARE_DEVICE = "HW"; 116 private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4; 117 private static final int LENGTH_HDMI_DEVICE_ID = 2; 118 119 private final ResolveInfo mService; 120 private final String mId; 121 private final String mParentId; 122 private final int mType; 123 private final boolean mIsHardwareInput; 124 125 // Attributes from XML meta data. 126 private String mSetupActivity; 127 private String mSettingsActivity; 128 129 private HdmiDeviceInfo mHdmiDeviceInfo; 130 private int mLabelRes; 131 private String mLabel; 132 private Icon mIcon; 133 private Uri mIconUri; 134 private boolean mIsConnectedToHdmiSwitch; 135 136 static { 137 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, 138 TYPE_OTHER); 139 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER); 140 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, TYPE_COMPOSITE); 141 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO); 142 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART); 143 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, TYPE_COMPONENT); 144 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA); 145 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI); 146 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI); 147 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT, 148 TYPE_DISPLAY_PORT); 149 } 150 151 /** 152 * Create a new instance of the TvInputInfo class, 153 * instantiating it from the given Context and ResolveInfo. 154 * 155 * @param service The ResolveInfo returned from the package manager about this TV input service. 156 * @hide 157 */ 158 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service) 159 throws XmlPullParserException, IOException { 160 return createTvInputInfo(context, service, generateInputIdForComponentName( 161 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)), 162 null, TYPE_TUNER, false, 0, null, null, null, false); 163 } 164 165 /** 166 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 167 * ResolveInfo, and HdmiDeviceInfo. 168 * 169 * @param service The ResolveInfo returned from the package manager about this TV input service. 170 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. 171 * @param parentId The ID of this TV input's parent input. {@code null} if none exists. 172 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} 173 * label will be loaded. 174 * @param iconUri The {@link android.net.Uri} to load the icon image. See 175 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, 176 * the application icon of {@code service} will be loaded. 177 * @hide 178 */ 179 @SystemApi 180 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 181 HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri) 182 throws XmlPullParserException, IOException { 183 boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0; 184 TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice( 185 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name), 186 hdmiDeviceInfo), parentId, TYPE_HDMI, true, 0, label, null, iconUri, 187 isConnectedToHdmiSwitch); 188 input.mHdmiDeviceInfo = hdmiDeviceInfo; 189 return input; 190 } 191 192 /** 193 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 194 * ResolveInfo, and HdmiDeviceInfo. 195 * 196 * @param service The ResolveInfo returned from the package manager about this TV input service. 197 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. 198 * @param parentId The ID of this TV input's parent input. {@code null} if none exists. 199 * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, 200 * {@code service} label will be loaded. 201 * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is 202 * {@code null}, the application icon of {@code service} will be loaded. 203 * @hide 204 */ 205 @SystemApi 206 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 207 HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon) 208 throws XmlPullParserException, IOException { 209 boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0; 210 TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice( 211 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name), 212 hdmiDeviceInfo), parentId, TYPE_HDMI, true, labelRes, null, icon, null, 213 isConnectedToHdmiSwitch); 214 input.mHdmiDeviceInfo = hdmiDeviceInfo; 215 return input; 216 } 217 218 /** 219 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 220 * ResolveInfo, and TvInputHardwareInfo. 221 * 222 * @param service The ResolveInfo returned from the package manager about this TV input service. 223 * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. 224 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} 225 * label will be loaded. 226 * @param iconUri The {@link android.net.Uri} to load the icon image. See 227 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, 228 * the application icon of {@code service} will be loaded. 229 * @hide 230 */ 231 @SystemApi 232 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 233 TvInputHardwareInfo hardwareInfo, String label, Uri iconUri) 234 throws XmlPullParserException, IOException { 235 int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER); 236 return createTvInputInfo(context, service, generateInputIdForHardware( 237 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name), 238 hardwareInfo), null, inputType, true, 0, label, null, iconUri, false); 239 } 240 241 /** 242 * Create a new instance of the TvInputInfo class, instantiating it from the given Context, 243 * ResolveInfo, and TvInputHardwareInfo. 244 * 245 * @param service The ResolveInfo returned from the package manager about this TV input service. 246 * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. 247 * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, 248 * {@code service} label will be loaded. 249 * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is 250 * {@code null}, the application icon of {@code service} will be loaded. 251 * @hide 252 */ 253 @SystemApi 254 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, 255 TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon) 256 throws XmlPullParserException, IOException { 257 int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER); 258 return createTvInputInfo(context, service, generateInputIdForHardware( 259 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name), 260 hardwareInfo), null, inputType, true, labelRes, null, icon, null, false); 261 } 262 263 private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, String id, 264 String parentId, int inputType, boolean isHardwareInput, int labelRes, String label, 265 Icon icon, Uri iconUri, boolean isConnectedToHdmiSwitch) 266 throws XmlPullParserException, IOException { 267 ServiceInfo si = service.serviceInfo; 268 PackageManager pm = context.getPackageManager(); 269 XmlResourceParser parser = null; 270 try { 271 parser = si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA); 272 if (parser == null) { 273 throw new XmlPullParserException("No " + TvInputService.SERVICE_META_DATA 274 + " meta-data for " + si.name); 275 } 276 277 Resources res = pm.getResourcesForApplication(si.applicationInfo); 278 AttributeSet attrs = Xml.asAttributeSet(parser); 279 280 int type; 281 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 282 && type != XmlPullParser.START_TAG) { 283 } 284 285 String nodeName = parser.getName(); 286 if (!XML_START_TAG_NAME.equals(nodeName)) { 287 throw new XmlPullParserException( 288 "Meta-data does not start with tv-input-service tag in " + si.name); 289 } 290 291 TvInputInfo input = new TvInputInfo(service, id, parentId, inputType, isHardwareInput); 292 TypedArray sa = res.obtainAttributes(attrs, 293 com.android.internal.R.styleable.TvInputService); 294 input.mSetupActivity = sa.getString( 295 com.android.internal.R.styleable.TvInputService_setupActivity); 296 if (DEBUG) { 297 Log.d(TAG, "Setup activity loaded. [" + input.mSetupActivity + "] for " + si.name); 298 } 299 if (inputType == TYPE_TUNER && TextUtils.isEmpty(input.mSetupActivity)) { 300 throw new XmlPullParserException("Setup activity not found in " + si.name); 301 } 302 input.mSettingsActivity = sa.getString( 303 com.android.internal.R.styleable.TvInputService_settingsActivity); 304 if (DEBUG) { 305 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for " 306 + si.name); 307 } 308 sa.recycle(); 309 310 input.mLabelRes = labelRes; 311 input.mLabel = label; 312 input.mIcon = icon; 313 input.mIconUri = iconUri; 314 input.mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch; 315 return input; 316 } catch (NameNotFoundException e) { 317 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 318 } finally { 319 if (parser != null) { 320 parser.close(); 321 } 322 } 323 } 324 325 /** 326 * Constructor. 327 * 328 * @param service The ResolveInfo returned from the package manager about this TV input service. 329 * @param id ID of this TV input. Should be generated via generateInputId*(). 330 * @param parentId ID of this TV input's parent input. {@code null} if none exists. 331 * @param type The type of this TV input service. 332 * @param isHardwareInput {@code true} if this TV input represents a hardware device. 333 * {@code false} otherwise. 334 */ 335 private TvInputInfo(ResolveInfo service, String id, String parentId, int type, 336 boolean isHardwareInput) { 337 mService = service; 338 mId = id; 339 mParentId = parentId; 340 mType = type; 341 mIsHardwareInput = isHardwareInput; 342 } 343 344 /** 345 * Returns a unique ID for this TV input. The ID is generated from the package and class name 346 * implementing the TV input service. 347 */ 348 public String getId() { 349 return mId; 350 } 351 352 /** 353 * Returns the parent input ID. 354 * 355 * <p>A TV input may have a parent input if the TV input is actually a logical representation of 356 * a device behind the hardware port represented by the parent input. 357 * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV 358 * input. In this case, the parent input of this logical device is the HDMI port. 359 * 360 * <p>Applications may group inputs by parent input ID to provide an easier access to inputs 361 * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind 362 * the same HDMI port have the same parent ID, which is the ID representing the port. Thus 363 * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it 364 * together using this method. 365 * 366 * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is 367 * not specified. 368 */ 369 public String getParentId() { 370 return mParentId; 371 } 372 373 /** 374 * Returns the information of the service that implements this TV input. 375 */ 376 public ServiceInfo getServiceInfo() { 377 return mService.serviceInfo; 378 } 379 380 /** 381 * Returns the component of the service that implements this TV input. 382 * @hide 383 */ 384 public ComponentName getComponent() { 385 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); 386 } 387 388 /** 389 * Returns an intent to start the setup activity for this TV input. 390 */ 391 public Intent createSetupIntent() { 392 if (!TextUtils.isEmpty(mSetupActivity)) { 393 Intent intent = new Intent(Intent.ACTION_MAIN); 394 intent.setClassName(mService.serviceInfo.packageName, mSetupActivity); 395 intent.putExtra(EXTRA_INPUT_ID, getId()); 396 return intent; 397 } 398 return null; 399 } 400 401 /** 402 * Returns an intent to start the settings activity for this TV input. 403 */ 404 public Intent createSettingsIntent() { 405 if (!TextUtils.isEmpty(mSettingsActivity)) { 406 Intent intent = new Intent(Intent.ACTION_MAIN); 407 intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity); 408 intent.putExtra(EXTRA_INPUT_ID, getId()); 409 return intent; 410 } 411 return null; 412 } 413 414 /** 415 * Returns the type of this TV input. 416 */ 417 public int getType() { 418 return mType; 419 } 420 421 /** 422 * Returns the HDMI device information of this TV input. 423 * @hide 424 */ 425 @SystemApi 426 public HdmiDeviceInfo getHdmiDeviceInfo() { 427 if (mType == TYPE_HDMI) { 428 return mHdmiDeviceInfo; 429 } 430 return null; 431 } 432 433 /** 434 * Returns {@code true} if this TV input is pass-though which does not have any real channels in 435 * TvProvider. {@code false} otherwise. 436 * 437 * @see TvContract#buildChannelUriForPassthroughInput(String) 438 */ 439 public boolean isPassthroughInput() { 440 return mType != TYPE_TUNER; 441 } 442 443 /** 444 * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner, 445 * HDMI1) {@code false} otherwise. 446 * @hide 447 */ 448 @SystemApi 449 public boolean isHardwareInput() { 450 return mIsHardwareInput; 451 } 452 453 /** 454 * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e., 455 * the device isn't directly connected to a HDMI port. 456 * @hide 457 */ 458 @SystemApi 459 public boolean isConnectedToHdmiSwitch() { 460 return mIsConnectedToHdmiSwitch; 461 } 462 463 /** 464 * Checks if this TV input is marked hidden by the user in the settings. 465 * 466 * @param context Supplies a {@link Context} used to check if this TV input is hidden. 467 * @return {@code true} if the user marked this TV input hidden in settings. {@code false} 468 * otherwise. 469 * @hide 470 */ 471 @SystemApi 472 public boolean isHidden(Context context) { 473 return TvInputSettings.isHidden(context, mId, UserHandle.myUserId()); 474 } 475 476 /** 477 * Loads the user-displayed label for this TV input. 478 * 479 * @param context Supplies a {@link Context} used to load the label. 480 * @return a CharSequence containing the TV input's label. If the TV input does not have 481 * a label, its name is returned. 482 */ 483 public CharSequence loadLabel(@NonNull Context context) { 484 if (mLabelRes != 0) { 485 return context.getPackageManager().getText(mService.serviceInfo.packageName, mLabelRes, 486 null); 487 } else if (!TextUtils.isEmpty(mLabel)) { 488 return mLabel; 489 } 490 return mService.loadLabel(context.getPackageManager()); 491 } 492 493 /** 494 * Loads the custom label set by user in settings. 495 * 496 * @param context Supplies a {@link Context} used to load the custom label. 497 * @return a CharSequence containing the TV input's custom label. {@code null} if there is no 498 * custom label. 499 * @hide 500 */ 501 @SystemApi 502 public CharSequence loadCustomLabel(Context context) { 503 return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId()); 504 } 505 506 /** 507 * Loads the user-displayed icon for this TV input. 508 * 509 * @param context Supplies a {@link Context} used to load the icon. 510 * @return a Drawable containing the TV input's icon. If the TV input does not have an icon, 511 * application's icon is returned. If it's unavailable too, {@code null} is returned. 512 */ 513 public Drawable loadIcon(@NonNull Context context) { 514 if (mIcon != null) { 515 return mIcon.loadDrawable(context); 516 } else if (mIconUri != null) { 517 try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) { 518 Drawable drawable = Drawable.createFromStream(is, null); 519 if (drawable != null) { 520 return drawable; 521 } 522 } catch (IOException e) { 523 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e); 524 // Falls back. 525 } 526 } 527 return loadServiceIcon(context); 528 } 529 530 @Override 531 public int describeContents() { 532 return 0; 533 } 534 535 @Override 536 public int hashCode() { 537 return mId.hashCode(); 538 } 539 540 @Override 541 public boolean equals(Object o) { 542 if (o == this) { 543 return true; 544 } 545 546 if (!(o instanceof TvInputInfo)) { 547 return false; 548 } 549 550 TvInputInfo obj = (TvInputInfo) o; 551 return mId.equals(obj.mId); 552 } 553 554 @Override 555 public String toString() { 556 return "TvInputInfo{id=" + mId 557 + ", pkg=" + mService.serviceInfo.packageName 558 + ", service=" + mService.serviceInfo.name + "}"; 559 } 560 561 /** 562 * Used to package this object into a {@link Parcel}. 563 * 564 * @param dest The {@link Parcel} to be written. 565 * @param flags The flags used for parceling. 566 */ 567 @Override 568 public void writeToParcel(@NonNull Parcel dest, int flags) { 569 dest.writeString(mId); 570 dest.writeString(mParentId); 571 mService.writeToParcel(dest, flags); 572 dest.writeString(mSetupActivity); 573 dest.writeString(mSettingsActivity); 574 dest.writeInt(mType); 575 dest.writeByte(mIsHardwareInput ? (byte) 1 : 0); 576 dest.writeParcelable(mHdmiDeviceInfo, flags); 577 dest.writeParcelable(mIcon, flags); 578 dest.writeParcelable(mIconUri, flags); 579 dest.writeInt(mLabelRes); 580 dest.writeString(mLabel); 581 dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); 582 } 583 584 private Drawable loadServiceIcon(Context context) { 585 if (mService.serviceInfo.icon == 0 586 && mService.serviceInfo.applicationInfo.icon == 0) { 587 return null; 588 } 589 return mService.serviceInfo.loadIcon(context.getPackageManager()); 590 } 591 592 /** 593 * Used to generate an input id from a ComponentName. 594 * 595 * @param name the component name for generating an input id. 596 * @return the generated input id for the given {@code name}. 597 */ 598 private static String generateInputIdForComponentName(ComponentName name) { 599 return name.flattenToShortString(); 600 } 601 602 /** 603 * Used to generate an input id from a ComponentName and HdmiDeviceInfo. 604 * 605 * @param name the component name for generating an input id. 606 * @param deviceInfo HdmiDeviceInfo describing this TV input. 607 * @return the generated input id for the given {@code name} and {@code deviceInfo}. 608 */ 609 private static String generateInputIdForHdmiDevice( 610 ComponentName name, HdmiDeviceInfo deviceInfo) { 611 // Example of the format : "/HDMI%04X%02X" 612 String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE 613 + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X" 614 + "%0" + LENGTH_HDMI_DEVICE_ID + "X"; 615 return name.flattenToShortString() + String.format(Locale.ENGLISH, format, 616 deviceInfo.getPhysicalAddress(), deviceInfo.getId()); 617 } 618 619 /** 620 * Used to generate an input id from a ComponentName and TvInputHardwareInfo 621 * 622 * @param name the component name for generating an input id. 623 * @param hardwareInfo TvInputHardwareInfo describing this TV input. 624 * @return the generated input id for the given {@code name} and {@code hardwareInfo}. 625 */ 626 private static String generateInputIdForHardware( 627 ComponentName name, TvInputHardwareInfo hardwareInfo) { 628 return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE 629 + hardwareInfo.getDeviceId(); 630 } 631 632 public static final Parcelable.Creator<TvInputInfo> CREATOR = 633 new Parcelable.Creator<TvInputInfo>() { 634 @Override 635 public TvInputInfo createFromParcel(Parcel in) { 636 return new TvInputInfo(in); 637 } 638 639 @Override 640 public TvInputInfo[] newArray(int size) { 641 return new TvInputInfo[size]; 642 } 643 }; 644 645 private TvInputInfo(Parcel in) { 646 mId = in.readString(); 647 mParentId = in.readString(); 648 mService = ResolveInfo.CREATOR.createFromParcel(in); 649 mSetupActivity = in.readString(); 650 mSettingsActivity = in.readString(); 651 mType = in.readInt(); 652 mIsHardwareInput = in.readByte() == 1; 653 mHdmiDeviceInfo = in.readParcelable(null); 654 mIcon = in.readParcelable(null); 655 mIconUri = in.readParcelable(null); 656 mLabelRes = in.readInt(); 657 mLabel = in.readString(); 658 mIsConnectedToHdmiSwitch = in.readByte() == 1; 659 } 660 661 /** 662 * Utility class for putting and getting settings for TV input. 663 * 664 * @hide 665 */ 666 @SystemApi 667 public static final class TvInputSettings { 668 private static final String TV_INPUT_SEPARATOR = ":"; 669 private static final String CUSTOM_NAME_SEPARATOR = ","; 670 671 private TvInputSettings() { } 672 673 private static boolean isHidden(Context context, String inputId, int userId) { 674 return getHiddenTvInputIds(context, userId).contains(inputId); 675 } 676 677 private static String getCustomLabel(Context context, String inputId, int userId) { 678 return getCustomLabels(context, userId).get(inputId); 679 } 680 681 /** 682 * Returns a set of TV input IDs which are marked as hidden by user in the settings. 683 * 684 * @param context The application context 685 * @param userId The user ID for the stored hidden input set 686 * @hide 687 */ 688 @SystemApi 689 public static Set<String> getHiddenTvInputIds(Context context, int userId) { 690 String hiddenIdsString = Settings.Secure.getStringForUser( 691 context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId); 692 Set<String> set = new HashSet<>(); 693 if (TextUtils.isEmpty(hiddenIdsString)) { 694 return set; 695 } 696 String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR); 697 for (String id : ids) { 698 set.add(Uri.decode(id)); 699 } 700 return set; 701 } 702 703 /** 704 * Returns a map of TV input ID/custom label pairs set by the user in the settings. 705 * 706 * @param context The application context 707 * @param userId The user ID for the stored hidden input map 708 * @hide 709 */ 710 @SystemApi 711 public static Map<String, String> getCustomLabels(Context context, int userId) { 712 String labelsString = Settings.Secure.getStringForUser( 713 context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId); 714 Map<String, String> map = new HashMap<>(); 715 if (TextUtils.isEmpty(labelsString)) { 716 return map; 717 } 718 String[] pairs = labelsString.split(TV_INPUT_SEPARATOR); 719 for (String pairString : pairs) { 720 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR); 721 map.put(Uri.decode(pair[0]), Uri.decode(pair[1])); 722 } 723 return map; 724 } 725 726 /** 727 * Stores a set of TV input IDs which are marked as hidden by user. This is expected to 728 * be called from the settings app. 729 * 730 * @param context The application context 731 * @param hiddenInputIds A set including all the hidden TV input IDs 732 * @param userId The user ID for the stored hidden input set 733 * @hide 734 */ 735 @SystemApi 736 public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds, 737 int userId) { 738 StringBuilder builder = new StringBuilder(); 739 boolean firstItem = true; 740 for (String inputId : hiddenInputIds) { 741 ensureValidField(inputId); 742 if (firstItem) { 743 firstItem = false; 744 } else { 745 builder.append(TV_INPUT_SEPARATOR); 746 } 747 builder.append(Uri.encode(inputId)); 748 } 749 Settings.Secure.putStringForUser(context.getContentResolver(), 750 Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId); 751 } 752 753 /** 754 * Stores a map of TV input ID/custom label set by user. This is expected to be 755 * called from the settings app. 756 * 757 * @param context The application context. 758 * @param customLabels A map of TV input ID/custom label pairs 759 * @param userId The user ID for the stored hidden input map 760 * @hide 761 */ 762 @SystemApi 763 public static void putCustomLabels(Context context, 764 Map<String, String> customLabels, int userId) { 765 StringBuilder builder = new StringBuilder(); 766 boolean firstItem = true; 767 for (Map.Entry<String, String> entry: customLabels.entrySet()) { 768 ensureValidField(entry.getKey()); 769 ensureValidField(entry.getValue()); 770 if (firstItem) { 771 firstItem = false; 772 } else { 773 builder.append(TV_INPUT_SEPARATOR); 774 } 775 builder.append(Uri.encode(entry.getKey())); 776 builder.append(CUSTOM_NAME_SEPARATOR); 777 builder.append(Uri.encode(entry.getValue())); 778 } 779 Settings.Secure.putStringForUser(context.getContentResolver(), 780 Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId); 781 } 782 783 private static void ensureValidField(String value) { 784 if (TextUtils.isEmpty(value)) { 785 throw new IllegalArgumentException(value + " should not empty "); 786 } 787 } 788 } 789 } 790