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); 657 mLabelResId = in.readInt(); 658 mIcon = in.readParcelable(null); 659 mIconStandby = in.readParcelable(null); 660 mIconDisconnected = in.readParcelable(null); 661 mSetupActivity = in.readString(); 662 mCanRecord = in.readByte() == 1; 663 mCanPauseRecording = in.readByte() == 1; 664 mTunerCount = in.readInt(); 665 mHdmiDeviceInfo = in.readParcelable(null); 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 = 943 hdmiConnectionRelativePosition 944 != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; 945 } else if (mTvInputHardwareInfo != null) { 946 id = generateInputId(componentName, mTvInputHardwareInfo); 947 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); 948 isHardwareInput = true; 949 } else { 950 id = generateInputId(componentName); 951 type = TYPE_TUNER; 952 } 953 parseServiceMetadata(type); 954 return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, 955 mIcon, mIconStandby, mIconDisconnected, mSetupActivity, 956 mCanRecord == null ? false : mCanRecord, 957 mCanPauseRecording == null ? false : mCanPauseRecording, 958 mTunerCount == null ? 0 : mTunerCount, 959 mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition, 960 mParentId, mExtras); 961 } 962 generateInputId(ComponentName name)963 private static String generateInputId(ComponentName name) { 964 return name.flattenToShortString(); 965 } 966 generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo)967 private static String generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo) { 968 // Example of the format : "/HDMI%04X%02X" 969 String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE 970 + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X" 971 + "%0" + LENGTH_HDMI_DEVICE_ID + "X"; 972 return name.flattenToShortString() + String.format(Locale.ENGLISH, format, 973 hdmiDeviceInfo.getPhysicalAddress(), hdmiDeviceInfo.getId()); 974 } 975 generateInputId(ComponentName name, TvInputHardwareInfo tvInputHardwareInfo)976 private static String generateInputId(ComponentName name, 977 TvInputHardwareInfo tvInputHardwareInfo) { 978 return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE 979 + tvInputHardwareInfo.getDeviceId(); 980 } 981 getRelativePosition(Context context, HdmiDeviceInfo info)982 private static int getRelativePosition(Context context, HdmiDeviceInfo info) { 983 HdmiControlManager hcm = 984 (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE); 985 if (hcm == null) { 986 return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; 987 } 988 return HdmiUtils.getHdmiAddressRelativePosition( 989 info.getPhysicalAddress(), hcm.getPhysicalAddress()); 990 } 991 parseServiceMetadata(int inputType)992 private void parseServiceMetadata(int inputType) { 993 ServiceInfo si = mResolveInfo.serviceInfo; 994 PackageManager pm = mContext.getPackageManager(); 995 try (XmlResourceParser parser = 996 si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA)) { 997 if (parser == null) { 998 throw new IllegalStateException("No " + TvInputService.SERVICE_META_DATA 999 + " meta-data found for " + si.name); 1000 } 1001 1002 Resources res = pm.getResourcesForApplication(si.applicationInfo); 1003 AttributeSet attrs = Xml.asAttributeSet(parser); 1004 1005 int type; 1006 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 1007 && type != XmlPullParser.START_TAG) { 1008 } 1009 1010 String nodeName = parser.getName(); 1011 if (!XML_START_TAG_NAME.equals(nodeName)) { 1012 throw new IllegalStateException("Meta-data does not start with " 1013 + XML_START_TAG_NAME + " tag for " + si.name); 1014 } 1015 1016 TypedArray sa = res.obtainAttributes(attrs, 1017 com.android.internal.R.styleable.TvInputService); 1018 mSetupActivity = sa.getString( 1019 com.android.internal.R.styleable.TvInputService_setupActivity); 1020 if (mCanRecord == null) { 1021 mCanRecord = sa.getBoolean( 1022 com.android.internal.R.styleable.TvInputService_canRecord, false); 1023 } 1024 if (mTunerCount == null && inputType == TYPE_TUNER) { 1025 mTunerCount = sa.getInt( 1026 com.android.internal.R.styleable.TvInputService_tunerCount, 1); 1027 } 1028 if (mCanPauseRecording == null) { 1029 mCanPauseRecording = sa.getBoolean( 1030 com.android.internal.R.styleable.TvInputService_canPauseRecording, 1031 false); 1032 } 1033 1034 sa.recycle(); 1035 } catch (IOException | XmlPullParserException e) { 1036 throw new IllegalStateException("Failed reading meta-data for " + si.packageName, e); 1037 } catch (NameNotFoundException e) { 1038 throw new IllegalStateException("No resources found for " + si.packageName, e); 1039 } 1040 } 1041 } 1042 1043 /** 1044 * Utility class for putting and getting settings for TV input. 1045 * 1046 * @hide 1047 */ 1048 @SystemApi 1049 public static final class TvInputSettings { 1050 private static final String TV_INPUT_SEPARATOR = ":"; 1051 private static final String CUSTOM_NAME_SEPARATOR = ","; 1052 TvInputSettings()1053 private TvInputSettings() { } 1054 isHidden(Context context, String inputId, int userId)1055 private static boolean isHidden(Context context, String inputId, int userId) { 1056 return getHiddenTvInputIds(context, userId).contains(inputId); 1057 } 1058 getCustomLabel(Context context, String inputId, int userId)1059 private static String getCustomLabel(Context context, String inputId, int userId) { 1060 return getCustomLabels(context, userId).get(inputId); 1061 } 1062 1063 /** 1064 * Returns a set of TV input IDs which are marked as hidden by user in the settings. 1065 * 1066 * @param context The application context 1067 * @param userId The user ID for the stored hidden input set 1068 * @hide 1069 */ 1070 @SystemApi getHiddenTvInputIds(Context context, int userId)1071 public static Set<String> getHiddenTvInputIds(Context context, int userId) { 1072 String hiddenIdsString = Settings.Secure.getStringForUser( 1073 context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId); 1074 Set<String> set = new HashSet<>(); 1075 if (TextUtils.isEmpty(hiddenIdsString)) { 1076 return set; 1077 } 1078 String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR); 1079 for (String id : ids) { 1080 set.add(Uri.decode(id)); 1081 } 1082 return set; 1083 } 1084 1085 /** 1086 * Returns a map of TV input ID/custom label pairs set by the user in the settings. 1087 * 1088 * @param context The application context 1089 * @param userId The user ID for the stored hidden input map 1090 * @hide 1091 */ 1092 @SystemApi getCustomLabels(Context context, int userId)1093 public static Map<String, String> getCustomLabels(Context context, int userId) { 1094 String labelsString = Settings.Secure.getStringForUser( 1095 context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId); 1096 Map<String, String> map = new HashMap<>(); 1097 if (TextUtils.isEmpty(labelsString)) { 1098 return map; 1099 } 1100 String[] pairs = labelsString.split(TV_INPUT_SEPARATOR); 1101 for (String pairString : pairs) { 1102 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR); 1103 map.put(Uri.decode(pair[0]), Uri.decode(pair[1])); 1104 } 1105 return map; 1106 } 1107 1108 /** 1109 * Stores a set of TV input IDs which are marked as hidden by user. This is expected to 1110 * be called from the settings app. 1111 * 1112 * @param context The application context 1113 * @param hiddenInputIds A set including all the hidden TV input IDs 1114 * @param userId The user ID for the stored hidden input set 1115 * @hide 1116 */ 1117 @SystemApi putHiddenTvInputs(Context context, Set<String> hiddenInputIds, int userId)1118 public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds, 1119 int userId) { 1120 StringBuilder builder = new StringBuilder(); 1121 boolean firstItem = true; 1122 for (String inputId : hiddenInputIds) { 1123 ensureValidField(inputId); 1124 if (firstItem) { 1125 firstItem = false; 1126 } else { 1127 builder.append(TV_INPUT_SEPARATOR); 1128 } 1129 builder.append(Uri.encode(inputId)); 1130 } 1131 Settings.Secure.putStringForUser(context.getContentResolver(), 1132 Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId); 1133 1134 // Notify of the TvInputInfo changes. 1135 TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 1136 for (String inputId : hiddenInputIds) { 1137 TvInputInfo info = tm.getTvInputInfo(inputId); 1138 if (info != null) { 1139 tm.updateTvInputInfo(info); 1140 } 1141 } 1142 } 1143 1144 /** 1145 * Stores a map of TV input ID/custom label set by user. This is expected to be 1146 * called from the settings app. 1147 * 1148 * @param context The application context. 1149 * @param customLabels A map of TV input ID/custom label pairs 1150 * @param userId The user ID for the stored hidden input map 1151 * @hide 1152 */ 1153 @SystemApi putCustomLabels(Context context, Map<String, String> customLabels, int userId)1154 public static void putCustomLabels(Context context, 1155 Map<String, String> customLabels, int userId) { 1156 StringBuilder builder = new StringBuilder(); 1157 boolean firstItem = true; 1158 for (Map.Entry<String, String> entry: customLabels.entrySet()) { 1159 ensureValidField(entry.getKey()); 1160 ensureValidField(entry.getValue()); 1161 if (firstItem) { 1162 firstItem = false; 1163 } else { 1164 builder.append(TV_INPUT_SEPARATOR); 1165 } 1166 builder.append(Uri.encode(entry.getKey())); 1167 builder.append(CUSTOM_NAME_SEPARATOR); 1168 builder.append(Uri.encode(entry.getValue())); 1169 } 1170 Settings.Secure.putStringForUser(context.getContentResolver(), 1171 Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId); 1172 1173 // Notify of the TvInputInfo changes. 1174 TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 1175 for (String inputId : customLabels.keySet()) { 1176 TvInputInfo info = tm.getTvInputInfo(inputId); 1177 if (info != null) { 1178 tm.updateTvInputInfo(info); 1179 } 1180 } 1181 } 1182 ensureValidField(String value)1183 private static void ensureValidField(String value) { 1184 if (TextUtils.isEmpty(value)) { 1185 throw new IllegalArgumentException(value + " should not empty "); 1186 } 1187 } 1188 } 1189 } 1190