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.SystemApi; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.pm.ResolveInfo; 26 import android.content.pm.ServiceInfo; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.content.res.XmlResourceParser; 30 import android.graphics.drawable.Drawable; 31 import android.hardware.hdmi.HdmiDeviceInfo; 32 import android.net.Uri; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.UserHandle; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.util.AttributeSet; 39 import android.util.Log; 40 import android.util.SparseIntArray; 41 import android.util.Xml; 42 43 import org.xmlpull.v1.XmlPullParser; 44 import org.xmlpull.v1.XmlPullParserException; 45 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.Map; 51 import java.util.Set; 52 53 /** 54 * This class is used to specify meta information of a TV input. 55 */ 56 public 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 if (inputType == TYPE_TUNER && TextUtils.isEmpty(input.mSetupActivity)) { 245 throw new XmlPullParserException("Setup activity not found in " + si.name); 246 } 247 input.mSettingsActivity = sa.getString( 248 com.android.internal.R.styleable.TvInputService_settingsActivity); 249 if (DEBUG) { 250 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for " 251 + si.name); 252 } 253 sa.recycle(); 254 255 input.mLabel = label; 256 input.mIconUri = iconUri; 257 input.mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch; 258 return input; 259 } catch (NameNotFoundException e) { 260 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 261 } finally { 262 if (parser != null) { 263 parser.close(); 264 } 265 } 266 } 267 268 /** 269 * Constructor. 270 * 271 * @param service The ResolveInfo returned from the package manager about this TV input service. 272 * @param id ID of this TV input. Should be generated via generateInputId*(). 273 * @param parentId ID of this TV input's parent input. {@code null} if none exists. 274 * @param type The type of this TV input service. 275 */ 276 private TvInputInfo(ResolveInfo service, String id, String parentId, int type) { 277 mService = service; 278 mId = id; 279 mParentId = parentId; 280 mType = type; 281 } 282 283 /** 284 * Returns a unique ID for this TV input. The ID is generated from the package and class name 285 * implementing the TV input service. 286 */ 287 public String getId() { 288 return mId; 289 } 290 291 /** 292 * Returns the parent input ID. 293 * <p> 294 * A TV input may have a parent input if the TV input is actually a logical representation of 295 * a device behind the hardware port represented by the parent input. 296 * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV 297 * input. In this case, the parent input of this logical device is the HDMI port. 298 * </p><p> 299 * Applications may group inputs by parent input ID to provide an easier access to inputs 300 * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind 301 * the same HDMI port have the same parent ID, which is the ID representing the port. Thus 302 * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it 303 * together using this method. 304 * </p> 305 * 306 * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is 307 * not specified. 308 */ 309 public String getParentId() { 310 return mParentId; 311 } 312 313 /** 314 * Returns the information of the service that implements this TV input. 315 */ 316 public ServiceInfo getServiceInfo() { 317 return mService.serviceInfo; 318 } 319 320 /** 321 * Returns the component of the service that implements this TV input. 322 * @hide 323 */ 324 public ComponentName getComponent() { 325 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); 326 } 327 328 /** 329 * Returns an intent to start the setup activity for this TV input. 330 */ 331 public Intent createSetupIntent() { 332 if (!TextUtils.isEmpty(mSetupActivity)) { 333 Intent intent = new Intent(Intent.ACTION_MAIN); 334 intent.setClassName(mService.serviceInfo.packageName, mSetupActivity); 335 intent.putExtra(EXTRA_INPUT_ID, getId()); 336 return intent; 337 } 338 return null; 339 } 340 341 /** 342 * Returns an intent to start the settings activity for this TV input. 343 */ 344 public Intent createSettingsIntent() { 345 if (!TextUtils.isEmpty(mSettingsActivity)) { 346 Intent intent = new Intent(Intent.ACTION_MAIN); 347 intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity); 348 intent.putExtra(EXTRA_INPUT_ID, getId()); 349 return intent; 350 } 351 return null; 352 } 353 354 /** 355 * Returns the type of this TV input. 356 */ 357 public int getType() { 358 return mType; 359 } 360 361 /** 362 * Returns the HDMI device information of this TV input. 363 * @hide 364 */ 365 @SystemApi 366 public HdmiDeviceInfo getHdmiDeviceInfo() { 367 if (mType == TYPE_HDMI) { 368 return mHdmiDeviceInfo; 369 } 370 return null; 371 } 372 373 /** 374 * Returns {@code true} if this TV input is pass-though which does not have any real channels in 375 * TvProvider. {@code false} otherwise. 376 * 377 * @see TvContract#buildChannelUriForPassthroughInput(String) 378 */ 379 public boolean isPassthroughInput() { 380 return mType != TYPE_TUNER; 381 } 382 383 /** 384 * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e., 385 * the device isn't directly connected to a HDMI port. 386 * @hide 387 */ 388 @SystemApi 389 public boolean isConnectedToHdmiSwitch() { 390 return mIsConnectedToHdmiSwitch; 391 } 392 393 /** 394 * Checks if this TV input is marked hidden by the user in the settings. 395 * 396 * @param context Supplies a {@link Context} used to check if this TV input is hidden. 397 * @return {@code true} if the user marked this TV input hidden in settings. {@code false} 398 * otherwise. 399 * @hide 400 */ 401 @SystemApi 402 public boolean isHidden(Context context) { 403 return TvInputSettings.isHidden(context, mId, UserHandle.myUserId()); 404 } 405 406 /** 407 * Loads the user-displayed label for this TV input. 408 * 409 * @param context Supplies a {@link Context} used to load the label. 410 * @return a CharSequence containing the TV input's label. If the TV input does not have 411 * a label, its name is returned. 412 */ 413 public CharSequence loadLabel(Context context) { 414 if (TextUtils.isEmpty(mLabel)) { 415 return mService.loadLabel(context.getPackageManager()); 416 } else { 417 return mLabel; 418 } 419 } 420 421 /** 422 * Loads the custom label set by user in settings. 423 * 424 * @param context Supplies a {@link Context} used to load the custom label. 425 * @return a CharSequence containing the TV input's custom label. {@code null} if there is no 426 * custom label. 427 * @hide 428 */ 429 @SystemApi 430 public CharSequence loadCustomLabel(Context context) { 431 return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId()); 432 } 433 434 /** 435 * Loads the user-displayed icon for this TV input. 436 * 437 * @param context Supplies a {@link Context} used to load the icon. 438 * @return a Drawable containing the TV input's icon. If the TV input does not have an icon, 439 * application's icon is returned. If it's unavailable too, {@code null} is returned. 440 */ 441 public Drawable loadIcon(Context context) { 442 if (mIconUri == null) { 443 return loadServiceIcon(context); 444 } 445 try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) { 446 Drawable drawable = Drawable.createFromStream(is, null); 447 if (drawable == null) { 448 return loadServiceIcon(context); 449 } 450 return drawable; 451 } catch (IOException e) { 452 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e); 453 return loadServiceIcon(context); 454 } 455 } 456 457 @Override 458 public int describeContents() { 459 return 0; 460 } 461 462 @Override 463 public int hashCode() { 464 return mId.hashCode(); 465 } 466 467 @Override 468 public boolean equals(Object o) { 469 if (o == this) { 470 return true; 471 } 472 473 if (!(o instanceof TvInputInfo)) { 474 return false; 475 } 476 477 TvInputInfo obj = (TvInputInfo) o; 478 return mId.equals(obj.mId); 479 } 480 481 @Override 482 public String toString() { 483 return "TvInputInfo{id=" + mId 484 + ", pkg=" + mService.serviceInfo.packageName 485 + ", service=" + mService.serviceInfo.name + "}"; 486 } 487 488 /** 489 * Used to package this object into a {@link Parcel}. 490 * 491 * @param dest The {@link Parcel} to be written. 492 * @param flags The flags used for parceling. 493 */ 494 @Override 495 public void writeToParcel(Parcel dest, int flags) { 496 dest.writeString(mId); 497 dest.writeString(mParentId); 498 mService.writeToParcel(dest, flags); 499 dest.writeString(mSetupActivity); 500 dest.writeString(mSettingsActivity); 501 dest.writeInt(mType); 502 dest.writeParcelable(mHdmiDeviceInfo, flags); 503 dest.writeParcelable(mIconUri, flags); 504 dest.writeString(mLabel); 505 dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); 506 } 507 508 private Drawable loadServiceIcon(Context context) { 509 if (mService.serviceInfo.icon == 0 510 && mService.serviceInfo.applicationInfo.icon == 0) { 511 return null; 512 } 513 return mService.serviceInfo.loadIcon(context.getPackageManager()); 514 } 515 516 /** 517 * Used to generate an input id from a ComponentName. 518 * 519 * @param name the component name for generating an input id. 520 * @return the generated input id for the given {@code name}. 521 */ 522 private static final String generateInputIdForComponentName(ComponentName name) { 523 return name.flattenToShortString(); 524 } 525 526 /** 527 * Used to generate an input id from a ComponentName and HdmiDeviceInfo. 528 * 529 * @param name the component name for generating an input id. 530 * @param deviceInfo HdmiDeviceInfo describing this TV input. 531 * @return the generated input id for the given {@code name} and {@code deviceInfo}. 532 */ 533 private static final String generateInputIdForHdmiDevice( 534 ComponentName name, HdmiDeviceInfo deviceInfo) { 535 // Example of the format : "/HDMI%04X%02X" 536 String format = String.format("%s%s%%0%sX%%0%sX", DELIMITER_INFO_IN_ID, PREFIX_HDMI_DEVICE, 537 LENGTH_HDMI_PHYSICAL_ADDRESS, LENGTH_HDMI_DEVICE_ID); 538 return name.flattenToShortString() + String.format(format, 539 deviceInfo.getPhysicalAddress(), deviceInfo.getId()); 540 } 541 542 /** 543 * Used to generate an input id from a ComponentName and TvInputHardwareInfo 544 * 545 * @param name the component name for generating an input id. 546 * @param hardwareInfo TvInputHardwareInfo describing this TV input. 547 * @return the generated input id for the given {@code name} and {@code hardwareInfo}. 548 */ 549 private static final String generateInputIdForHardware( 550 ComponentName name, TvInputHardwareInfo hardwareInfo) { 551 return name.flattenToShortString() + String.format("%s%s%d", 552 DELIMITER_INFO_IN_ID, PREFIX_HARDWARE_DEVICE, hardwareInfo.getDeviceId()); 553 } 554 555 public static final Parcelable.Creator<TvInputInfo> CREATOR = 556 new Parcelable.Creator<TvInputInfo>() { 557 @Override 558 public TvInputInfo createFromParcel(Parcel in) { 559 return new TvInputInfo(in); 560 } 561 562 @Override 563 public TvInputInfo[] newArray(int size) { 564 return new TvInputInfo[size]; 565 } 566 }; 567 568 private TvInputInfo(Parcel in) { 569 mId = in.readString(); 570 mParentId = in.readString(); 571 mService = ResolveInfo.CREATOR.createFromParcel(in); 572 mSetupActivity = in.readString(); 573 mSettingsActivity = in.readString(); 574 mType = in.readInt(); 575 mHdmiDeviceInfo = in.readParcelable(null); 576 mIconUri = in.readParcelable(null); 577 mLabel = in.readString(); 578 mIsConnectedToHdmiSwitch = in.readByte() == 1 ? true : false; 579 } 580 581 /** 582 * Utility class for putting and getting settings for TV input. 583 * 584 * @hide 585 */ 586 @SystemApi 587 public static final class TvInputSettings { 588 private static final String TV_INPUT_SEPARATOR = ":"; 589 private static final String CUSTOM_NAME_SEPARATOR = ","; 590 591 private TvInputSettings() { } 592 593 private static boolean isHidden(Context context, String inputId, int userId) { 594 return getHiddenTvInputIds(context, userId).contains(inputId); 595 } 596 597 private static String getCustomLabel(Context context, String inputId, int userId) { 598 return getCustomLabels(context, userId).get(inputId); 599 } 600 601 /** 602 * Returns a set of TV input IDs which are marked as hidden by user in the settings. 603 * 604 * @param context The application context 605 * @param userId The user ID for the stored hidden input set 606 * @hide 607 */ 608 @SystemApi 609 public static Set<String> getHiddenTvInputIds(Context context, int userId) { 610 String hiddenIdsString = Settings.Secure.getStringForUser( 611 context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId); 612 Set<String> set = new HashSet<String>(); 613 if (TextUtils.isEmpty(hiddenIdsString)) { 614 return set; 615 } 616 String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR); 617 for (String id : ids) { 618 set.add(Uri.decode(id)); 619 } 620 return set; 621 } 622 623 /** 624 * Returns a map of TV input ID/custom label pairs set by the user in the settings. 625 * 626 * @param context The application context 627 * @param userId The user ID for the stored hidden input map 628 * @hide 629 */ 630 @SystemApi 631 public static Map<String, String> getCustomLabels(Context context, int userId) { 632 String labelsString = Settings.Secure.getStringForUser( 633 context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId); 634 Map<String, String> map = new HashMap<String, String>(); 635 if (TextUtils.isEmpty(labelsString)) { 636 return map; 637 } 638 String[] pairs = labelsString.split(TV_INPUT_SEPARATOR); 639 for (String pairString : pairs) { 640 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR); 641 map.put(Uri.decode(pair[0]), Uri.decode(pair[1])); 642 } 643 return map; 644 } 645 646 /** 647 * Stores a set of TV input IDs which are marked as hidden by user. This is expected to 648 * be called from the settings app. 649 * 650 * @param context The application context 651 * @param hiddenInputIds A set including all the hidden TV input IDs 652 * @param userId The user ID for the stored hidden input set 653 * @hide 654 */ 655 @SystemApi 656 public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds, 657 int userId) { 658 StringBuilder builder = new StringBuilder(); 659 boolean firstItem = true; 660 for (String inputId : hiddenInputIds) { 661 ensureValidField(inputId); 662 if (firstItem) { 663 firstItem = false; 664 } else { 665 builder.append(TV_INPUT_SEPARATOR); 666 } 667 builder.append(Uri.encode(inputId)); 668 } 669 Settings.Secure.putStringForUser(context.getContentResolver(), 670 Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId); 671 } 672 673 /** 674 * Stores a map of TV input ID/custom label set by user. This is expected to be 675 * called from the settings app. 676 * 677 * @param context The application context. 678 * @param customLabels A map of TV input ID/custom label pairs 679 * @param userId The user ID for the stored hidden input map 680 * @hide 681 */ 682 @SystemApi 683 public static void putCustomLabels(Context context, 684 Map<String, String> customLabels, int userId) { 685 StringBuilder builder = new StringBuilder(); 686 boolean firstItem = true; 687 for (Map.Entry<String, String> entry: customLabels.entrySet()) { 688 ensureValidField(entry.getKey()); 689 ensureValidField(entry.getValue()); 690 if (firstItem) { 691 firstItem = false; 692 } else { 693 builder.append(TV_INPUT_SEPARATOR); 694 } 695 builder.append(Uri.encode(entry.getKey())); 696 builder.append(CUSTOM_NAME_SEPARATOR); 697 builder.append(Uri.encode(entry.getValue())); 698 } 699 Settings.Secure.putStringForUser(context.getContentResolver(), 700 Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId); 701 } 702 703 private static void ensureValidField(String value) { 704 if (TextUtils.isEmpty(value)) { 705 throw new IllegalArgumentException(value + " should not empty "); 706 } 707 } 708 } 709 } 710