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