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