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