OSDN Git Service

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