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