1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.tv;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.StringRes;
22 import android.annotation.SystemApi;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.ServiceInfo;
31 import android.content.res.Resources;
32 import android.content.res.TypedArray;
33 import android.content.res.XmlResourceParser;
34 import android.graphics.drawable.Drawable;
35 import android.graphics.drawable.Icon;
36 import android.hardware.hdmi.HdmiControlManager;
37 import android.hardware.hdmi.HdmiDeviceInfo;
38 import android.hardware.hdmi.HdmiUtils;
39 import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.Parcel;
44 import android.os.Parcelable;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.text.TextUtils;
48 import android.util.AttributeSet;
49 import android.util.Log;
50 import android.util.SparseIntArray;
51 import android.util.Xml;
52 
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
55 
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Locale;
63 import java.util.Map;
64 import java.util.Objects;
65 import java.util.Set;
66 
67 /**
68  * This class is used to specify meta information of a TV input.
69  */
70 public final class TvInputInfo implements Parcelable {
71     private static final boolean DEBUG = false;
72     private static final String TAG = "TvInputInfo";
73 
74     /** @hide */
75     @Retention(RetentionPolicy.SOURCE)
76     @IntDef({TYPE_TUNER, TYPE_OTHER, TYPE_COMPOSITE, TYPE_SVIDEO, TYPE_SCART, TYPE_COMPONENT,
77             TYPE_VGA, TYPE_DVI, TYPE_HDMI, TYPE_DISPLAY_PORT})
78     public @interface Type {}
79 
80     // Should be in sync with frameworks/base/core/res/res/values/attrs.xml
81     /**
82      * TV input type: the TV input service is a tuner which provides channels.
83      */
84     public static final int TYPE_TUNER = 0;
85     /**
86      * TV input type: a generic hardware TV input type.
87      */
88     public static final int TYPE_OTHER = 1000;
89     /**
90      * TV input type: the TV input service represents a composite port.
91      */
92     public static final int TYPE_COMPOSITE = 1001;
93     /**
94      * TV input type: the TV input service represents a SVIDEO port.
95      */
96     public static final int TYPE_SVIDEO = 1002;
97     /**
98      * TV input type: the TV input service represents a SCART port.
99      */
100     public static final int TYPE_SCART = 1003;
101     /**
102      * TV input type: the TV input service represents a component port.
103      */
104     public static final int TYPE_COMPONENT = 1004;
105     /**
106      * TV input type: the TV input service represents a VGA port.
107      */
108     public static final int TYPE_VGA = 1005;
109     /**
110      * TV input type: the TV input service represents a DVI port.
111      */
112     public static final int TYPE_DVI = 1006;
113     /**
114      * TV input type: the TV input service is HDMI. (e.g. HDMI 1)
115      */
116     public static final int TYPE_HDMI = 1007;
117     /**
118      * TV input type: the TV input service represents a display port.
119      */
120     public static final int TYPE_DISPLAY_PORT = 1008;
121 
122     /**
123      * Used as a String extra field in setup intents created by {@link #createSetupIntent()} to
124      * supply the ID of a specific TV input to set up.
125      */
126     public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID";
127 
128     private final ResolveInfo mService;
129 
130     private final String mId;
131     private final int mType;
132     private final boolean mIsHardwareInput;
133 
134     // TODO: Remove mIconUri when createTvInputInfo() is removed.
135     private Uri mIconUri;
136 
137     private final CharSequence mLabel;
138     private final int mLabelResId;
139     private final Icon mIcon;
140     private final Icon mIconStandby;
141     private final Icon mIconDisconnected;
142 
143     // Attributes from XML meta data.
144     private final String mSetupActivity;
145     private final boolean mCanRecord;
146     private final boolean mCanPauseRecording;
147     private final int mTunerCount;
148 
149     // Attributes specific to HDMI
150     private final HdmiDeviceInfo mHdmiDeviceInfo;
151     private final boolean mIsConnectedToHdmiSwitch;
152     @HdmiAddressRelativePosition
153     private final int mHdmiConnectionRelativePosition;
154     private final String mParentId;
155 
156     private final Bundle mExtras;
157 
158     /**
159      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
160      * ResolveInfo, and HdmiDeviceInfo.
161      *
162      * @param service The ResolveInfo returned from the package manager about this TV input service.
163      * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
164      * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
165      * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
166      *            label will be loaded.
167      * @param iconUri The {@link android.net.Uri} to load the icon image. See
168      *            {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
169      *            the application icon of {@code service} will be loaded.
170      * @hide
171      * @deprecated Use {@link Builder} instead.
172      */
173     @Deprecated
174     @SystemApi
createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri)175     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
176             HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri)
177                     throws XmlPullParserException, IOException {
178         TvInputInfo info = new TvInputInfo.Builder(context, service)
179                 .setHdmiDeviceInfo(hdmiDeviceInfo)
180                 .setParentId(parentId)
181                 .setLabel(label)
182                 .build();
183         info.mIconUri = iconUri;
184         return info;
185     }
186 
187     /**
188      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
189      * ResolveInfo, and HdmiDeviceInfo.
190      *
191      * @param service The ResolveInfo returned from the package manager about this TV input service.
192      * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
193      * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
194      * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
195      *            {@code service} label will be loaded.
196      * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
197      *            {@code null}, the application icon of {@code service} will be loaded.
198      * @hide
199      * @deprecated Use {@link Builder} instead.
200      */
201     @Deprecated
202     @SystemApi
createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon)203     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
204             HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon)
205             throws XmlPullParserException, IOException {
206         return new TvInputInfo.Builder(context, service)
207                 .setHdmiDeviceInfo(hdmiDeviceInfo)
208                 .setParentId(parentId)
209                 .setLabel(labelRes)
210                 .setIcon(icon)
211                 .build();
212     }
213 
214     /**
215      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
216      * ResolveInfo, and TvInputHardwareInfo.
217      *
218      * @param service The ResolveInfo returned from the package manager about this TV input service.
219      * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
220      * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
221      *            label will be loaded.
222      * @param iconUri The {@link android.net.Uri} to load the icon image. See
223      *            {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
224      *            the application icon of {@code service} will be loaded.
225      * @hide
226      * @deprecated Use {@link Builder} instead.
227      */
228     @Deprecated
229     @SystemApi
createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, String label, Uri iconUri)230     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
231             TvInputHardwareInfo hardwareInfo, String label, Uri iconUri)
232                     throws XmlPullParserException, IOException {
233         TvInputInfo info = new TvInputInfo.Builder(context, service)
234                 .setTvInputHardwareInfo(hardwareInfo)
235                 .setLabel(label)
236                 .build();
237         info.mIconUri = iconUri;
238         return info;
239     }
240 
241     /**
242      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
243      * ResolveInfo, and TvInputHardwareInfo.
244      *
245      * @param service The ResolveInfo returned from the package manager about this TV input service.
246      * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
247      * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
248      *            {@code service} label will be loaded.
249      * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
250      *            {@code null}, the application icon of {@code service} will be loaded.
251      * @hide
252      * @deprecated Use {@link Builder} instead.
253      */
254     @Deprecated
255     @SystemApi
createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon)256     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
257             TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon)
258             throws XmlPullParserException, IOException {
259         return new TvInputInfo.Builder(context, service)
260                 .setTvInputHardwareInfo(hardwareInfo)
261                 .setLabel(labelRes)
262                 .setIcon(icon)
263                 .build();
264     }
265 
TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, String setupActivity, boolean canRecord, boolean canPauseRecording, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId, Bundle extras)266     private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
267             CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
268             String setupActivity, boolean canRecord, boolean canPauseRecording, int tunerCount,
269             HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch,
270             @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
271             Bundle extras) {
272         mService = service;
273         mId = id;
274         mType = type;
275         mIsHardwareInput = isHardwareInput;
276         mLabel = label;
277         mLabelResId = labelResId;
278         mIcon = icon;
279         mIconStandby = iconStandby;
280         mIconDisconnected = iconDisconnected;
281         mSetupActivity = setupActivity;
282         mCanRecord = canRecord;
283         mCanPauseRecording = canPauseRecording;
284         mTunerCount = tunerCount;
285         mHdmiDeviceInfo = hdmiDeviceInfo;
286         mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
287         mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition;
288         mParentId = parentId;
289         mExtras = extras;
290     }
291 
292     /**
293      * Returns a unique ID for this TV input. The ID is generated from the package and class name
294      * implementing the TV input service.
295      */
getId()296     public String getId() {
297         return mId;
298     }
299 
300     /**
301      * Returns the parent input ID.
302      *
303      * <p>A TV input may have a parent input if the TV input is actually a logical representation of
304      * a device behind the hardware port represented by the parent input.
305      * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV
306      * input. In this case, the parent input of this logical device is the HDMI port.
307      *
308      * <p>Applications may group inputs by parent input ID to provide an easier access to inputs
309      * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind
310      * the same HDMI port have the same parent ID, which is the ID representing the port. Thus
311      * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it
312      * together using this method.
313      *
314      * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is
315      *         not specified.
316      */
getParentId()317     public String getParentId() {
318         return mParentId;
319     }
320 
321     /**
322      * Returns the information of the service that implements this TV input.
323      */
getServiceInfo()324     public ServiceInfo getServiceInfo() {
325         return mService.serviceInfo;
326     }
327 
328     /**
329      * Returns the component of the service that implements this TV input.
330      * @hide
331      */
332     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getComponent()333     public ComponentName getComponent() {
334         return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
335     }
336 
337     /**
338      * Returns an intent to start the setup activity for this TV input.
339      */
createSetupIntent()340     public Intent createSetupIntent() {
341         if (!TextUtils.isEmpty(mSetupActivity)) {
342             Intent intent = new Intent(Intent.ACTION_MAIN);
343             intent.setClassName(mService.serviceInfo.packageName, mSetupActivity);
344             intent.putExtra(EXTRA_INPUT_ID, getId());
345             return intent;
346         }
347         return null;
348     }
349 
350     /**
351      * Returns an intent to start the settings activity for this TV input.
352      *
353      * @deprecated Use {@link #createSetupIntent()} instead. Settings activity is deprecated.
354      *             Use setup activity instead to provide settings.
355      */
356     @Deprecated
createSettingsIntent()357     public Intent createSettingsIntent() {
358         return null;
359     }
360 
361     /**
362      * Returns the type of this TV input.
363      */
364     @Type
getType()365     public int getType() {
366         return mType;
367     }
368 
369     /**
370      * Returns the number of tuners this TV input has.
371      *
372      * <p>This method is valid only for inputs of type {@link #TYPE_TUNER}. For inputs of other
373      * types, it returns 0.
374      *
375      * <p>Tuners correspond to physical/logical resources that allow reception of TV signal. Having
376      * <i>N</i> tuners means that the TV input is capable of receiving <i>N</i> different channels
377      * concurrently.
378      */
getTunerCount()379     public int getTunerCount() {
380         return mTunerCount;
381     }
382 
383     /**
384      * Returns {@code true} if this TV input can record TV programs, {@code false} otherwise.
385      */
canRecord()386     public boolean canRecord() {
387         return mCanRecord;
388     }
389 
390     /**
391      * Returns {@code true} if this TV input can pause recording TV programs,
392      * {@code false} otherwise.
393      */
canPauseRecording()394     public boolean canPauseRecording() {
395         return mCanPauseRecording;
396     }
397 
398     /**
399      * Returns domain-specific extras associated with this TV input.
400      */
getExtras()401     public Bundle getExtras() {
402         return mExtras;
403     }
404 
405     /**
406      * Returns the HDMI device information of this TV input.
407      * @hide
408      */
409     @SystemApi
getHdmiDeviceInfo()410     public HdmiDeviceInfo getHdmiDeviceInfo() {
411         if (mType == TYPE_HDMI) {
412             return mHdmiDeviceInfo;
413         }
414         return null;
415     }
416 
417     /**
418      * Returns {@code true} if this TV input is pass-though which does not have any real channels in
419      * TvProvider. {@code false} otherwise.
420      *
421      * @see TvContract#buildChannelUriForPassthroughInput(String)
422      */
isPassthroughInput()423     public boolean isPassthroughInput() {
424         return mType != TYPE_TUNER;
425     }
426 
427     /**
428      * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner,
429      * HDMI1) {@code false} otherwise.
430      * @hide
431      */
432     @SystemApi
isHardwareInput()433     public boolean isHardwareInput() {
434         return mIsHardwareInput;
435     }
436 
437     /**
438      * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
439      * the device isn't directly connected to a HDMI port.
440      * TODO(b/110094868): add @Deprecated for Q
441      * @hide
442      */
443     @SystemApi
isConnectedToHdmiSwitch()444     public boolean isConnectedToHdmiSwitch() {
445         return mIsConnectedToHdmiSwitch;
446     }
447 
448     /**
449      * Returns the relative position of this HDMI input.
450      * TODO(b/110094868): unhide for Q
451      * @hide
452      */
453     @HdmiAddressRelativePosition
getHdmiConnectionRelativePosition()454     public int getHdmiConnectionRelativePosition() {
455         return mHdmiConnectionRelativePosition;
456     }
457 
458     /**
459      * Checks if this TV input is marked hidden by the user in the settings.
460      *
461      * @param context Supplies a {@link Context} used to check if this TV input is hidden.
462      * @return {@code true} if the user marked this TV input hidden in settings. {@code false}
463      *         otherwise.
464      */
isHidden(Context context)465     public boolean isHidden(Context context) {
466         return TvInputSettings.isHidden(context, mId, UserHandle.myUserId());
467     }
468 
469     /**
470      * Loads the user-displayed label for this TV input.
471      *
472      * @param context Supplies a {@link Context} used to load the label.
473      * @return a CharSequence containing the TV input's label. If the TV input does not have
474      *         a label, its name is returned.
475      */
loadLabel(@onNull Context context)476     public CharSequence loadLabel(@NonNull Context context) {
477         if (mLabelResId != 0) {
478             return context.getPackageManager().getText(mService.serviceInfo.packageName,
479                     mLabelResId, null);
480         } else if (!TextUtils.isEmpty(mLabel)) {
481             return mLabel;
482         }
483         return mService.loadLabel(context.getPackageManager());
484     }
485 
486     /**
487      * Loads the custom label set by user in settings.
488      *
489      * @param context Supplies a {@link Context} used to load the custom label.
490      * @return a CharSequence containing the TV input's custom label. {@code null} if there is no
491      *         custom label.
492      */
loadCustomLabel(Context context)493     public CharSequence loadCustomLabel(Context context) {
494         return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId());
495     }
496 
497     /**
498      * Loads the user-displayed icon for this TV input.
499      *
500      * @param context Supplies a {@link Context} used to load the icon.
501      * @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
502      *         application's icon is returned. If it's unavailable too, {@code null} is returned.
503      */
loadIcon(@onNull Context context)504     public Drawable loadIcon(@NonNull Context context) {
505         if (mIcon != null) {
506             return mIcon.loadDrawable(context);
507         } else if (mIconUri != null) {
508             try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
509                 Drawable drawable = Drawable.createFromStream(is, null);
510                 if (drawable != null) {
511                     return drawable;
512                 }
513             } catch (IOException e) {
514                 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
515                 // Falls back.
516             }
517         }
518         return loadServiceIcon(context);
519     }
520 
521     /**
522      * Loads the user-displayed icon for this TV input per input state.
523      *
524      * @param context Supplies a {@link Context} used to load the icon.
525      * @param state The input state. Should be one of the followings.
526      *              {@link TvInputManager#INPUT_STATE_CONNECTED},
527      *              {@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} and
528      *              {@link TvInputManager#INPUT_STATE_DISCONNECTED}.
529      * @return a Drawable containing the TV input's icon for the given state or {@code null} if such
530      *         an icon is not defined.
531      * @hide
532      */
533     @SystemApi
loadIcon(@onNull Context context, int state)534     public Drawable loadIcon(@NonNull Context context, int state) {
535         if (state == TvInputManager.INPUT_STATE_CONNECTED) {
536             return loadIcon(context);
537         } else if (state == TvInputManager.INPUT_STATE_CONNECTED_STANDBY) {
538             if (mIconStandby != null) {
539                 return mIconStandby.loadDrawable(context);
540             }
541         } else if (state == TvInputManager.INPUT_STATE_DISCONNECTED) {
542             if (mIconDisconnected != null) {
543                 return mIconDisconnected.loadDrawable(context);
544             }
545         } else {
546             throw new IllegalArgumentException("Unknown state: " + state);
547         }
548         return null;
549     }
550 
551     @Override
describeContents()552     public int describeContents() {
553         return 0;
554     }
555 
556     @Override
hashCode()557     public int hashCode() {
558         return mId.hashCode();
559     }
560 
561     @Override
equals(Object o)562     public boolean equals(Object o) {
563         if (o == this) {
564             return true;
565         }
566 
567         if (!(o instanceof TvInputInfo)) {
568             return false;
569         }
570 
571         TvInputInfo obj = (TvInputInfo) o;
572         return Objects.equals(mService, obj.mService)
573                 && TextUtils.equals(mId, obj.mId)
574                 && mType == obj.mType
575                 && mIsHardwareInput == obj.mIsHardwareInput
576                 && TextUtils.equals(mLabel, obj.mLabel)
577                 && Objects.equals(mIconUri, obj.mIconUri)
578                 && mLabelResId == obj.mLabelResId
579                 && Objects.equals(mIcon, obj.mIcon)
580                 && Objects.equals(mIconStandby, obj.mIconStandby)
581                 && Objects.equals(mIconDisconnected, obj.mIconDisconnected)
582                 && TextUtils.equals(mSetupActivity, obj.mSetupActivity)
583                 && mCanRecord == obj.mCanRecord
584                 && mCanPauseRecording == obj.mCanPauseRecording
585                 && mTunerCount == obj.mTunerCount
586                 && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
587                 && mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
588                 && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition
589                 && TextUtils.equals(mParentId, obj.mParentId)
590                 && Objects.equals(mExtras, obj.mExtras);
591     }
592 
593     @Override
toString()594     public String toString() {
595         return "TvInputInfo{id=" + mId
596                 + ", pkg=" + mService.serviceInfo.packageName
597                 + ", service=" + mService.serviceInfo.name + "}";
598     }
599 
600     /**
601      * Used to package this object into a {@link Parcel}.
602      *
603      * @param dest The {@link Parcel} to be written.
604      * @param flags The flags used for parceling.
605      */
606     @Override
writeToParcel(@onNull Parcel dest, int flags)607     public void writeToParcel(@NonNull Parcel dest, int flags) {
608         mService.writeToParcel(dest, flags);
609         dest.writeString(mId);
610         dest.writeInt(mType);
611         dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
612         TextUtils.writeToParcel(mLabel, dest, flags);
613         dest.writeParcelable(mIconUri, flags);
614         dest.writeInt(mLabelResId);
615         dest.writeParcelable(mIcon, flags);
616         dest.writeParcelable(mIconStandby, flags);
617         dest.writeParcelable(mIconDisconnected, flags);
618         dest.writeString(mSetupActivity);
619         dest.writeByte(mCanRecord ? (byte) 1 : 0);
620         dest.writeByte(mCanPauseRecording ? (byte) 1 : 0);
621         dest.writeInt(mTunerCount);
622         dest.writeParcelable(mHdmiDeviceInfo, flags);
623         dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
624         dest.writeInt(mHdmiConnectionRelativePosition);
625         dest.writeString(mParentId);
626         dest.writeBundle(mExtras);
627     }
628 
loadServiceIcon(Context context)629     private Drawable loadServiceIcon(Context context) {
630         if (mService.serviceInfo.icon == 0
631                 && mService.serviceInfo.applicationInfo.icon == 0) {
632             return null;
633         }
634         return mService.serviceInfo.loadIcon(context.getPackageManager());
635     }
636 
637     public static final @android.annotation.NonNull Parcelable.Creator<TvInputInfo> CREATOR =
638             new Parcelable.Creator<TvInputInfo>() {
639         @Override
640         public TvInputInfo createFromParcel(Parcel in) {
641             return new TvInputInfo(in);
642         }
643 
644         @Override
645         public TvInputInfo[] newArray(int size) {
646             return new TvInputInfo[size];
647         }
648     };
649 
TvInputInfo(Parcel in)650     private TvInputInfo(Parcel in) {
651         mService = ResolveInfo.CREATOR.createFromParcel(in);
652         mId = in.readString();
653         mType = in.readInt();
654         mIsHardwareInput = in.readByte() == 1;
655         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
656         mIconUri = in.readParcelable(null, android.net.Uri.class);
657         mLabelResId = in.readInt();
658         mIcon = in.readParcelable(null, android.graphics.drawable.Icon.class);
659         mIconStandby = in.readParcelable(null, android.graphics.drawable.Icon.class);
660         mIconDisconnected = in.readParcelable(null, android.graphics.drawable.Icon.class);
661         mSetupActivity = in.readString();
662         mCanRecord = in.readByte() == 1;
663         mCanPauseRecording = in.readByte() == 1;
664         mTunerCount = in.readInt();
665         mHdmiDeviceInfo = in.readParcelable(null, android.hardware.hdmi.HdmiDeviceInfo.class);
666         mIsConnectedToHdmiSwitch = in.readByte() == 1;
667         mHdmiConnectionRelativePosition = in.readInt();
668         mParentId = in.readString();
669         mExtras = in.readBundle();
670     }
671 
672     /**
673      * A convenience builder for creating {@link TvInputInfo} objects.
674      */
675     public static final class Builder {
676         private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4;
677         private static final int LENGTH_HDMI_DEVICE_ID = 2;
678 
679         private static final String XML_START_TAG_NAME = "tv-input";
680         private static final String DELIMITER_INFO_IN_ID = "/";
681         private static final String PREFIX_HDMI_DEVICE = "HDMI";
682         private static final String PREFIX_HARDWARE_DEVICE = "HW";
683 
684         private static final SparseIntArray sHardwareTypeToTvInputType = new SparseIntArray();
685         static {
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, TYPE_OTHER)686             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE,
687                     TYPE_OTHER);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER)688             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, TYPE_COMPOSITE)689             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE,
690                     TYPE_COMPOSITE);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO)691             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART)692             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, TYPE_COMPONENT)693             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT,
694                     TYPE_COMPONENT);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA)695             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI)696             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI)697             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI);
sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT, TYPE_DISPLAY_PORT)698             sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT,
699                     TYPE_DISPLAY_PORT);
700         }
701 
702         private final Context mContext;
703         private final ResolveInfo mResolveInfo;
704         private CharSequence mLabel;
705         private int mLabelResId;
706         private Icon mIcon;
707         private Icon mIconStandby;
708         private Icon mIconDisconnected;
709         private String mSetupActivity;
710         private Boolean mCanRecord;
711         private Boolean mCanPauseRecording;
712         private Integer mTunerCount;
713         private TvInputHardwareInfo mTvInputHardwareInfo;
714         private HdmiDeviceInfo mHdmiDeviceInfo;
715         private String mParentId;
716         private Bundle mExtras;
717 
718         /**
719          * Constructs a new builder for {@link TvInputInfo}.
720          *
721          * @param context A Context of the application package implementing this class.
722          * @param component The name of the application component to be used for the
723          *            {@link TvInputService}.
724          */
Builder(Context context, ComponentName component)725         public Builder(Context context, ComponentName component) {
726             if (context == null) {
727                 throw new IllegalArgumentException("context cannot be null.");
728             }
729             Intent intent = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
730             mResolveInfo = context.getPackageManager().resolveService(intent,
731                     PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
732             if (mResolveInfo == null) {
733                 throw new IllegalArgumentException("Invalid component. Can't find the service.");
734             }
735             mContext = context;
736         }
737 
738         /**
739          * Constructs a new builder for {@link TvInputInfo}.
740          *
741          * @param resolveInfo The ResolveInfo returned from the package manager about this TV input
742          *            service.
743          * @hide
744          */
Builder(Context context, ResolveInfo resolveInfo)745         public Builder(Context context, ResolveInfo resolveInfo) {
746             if (context == null) {
747                 throw new IllegalArgumentException("context cannot be null");
748             }
749             if (resolveInfo == null) {
750                 throw new IllegalArgumentException("resolveInfo cannot be null");
751             }
752             mContext = context;
753             mResolveInfo = resolveInfo;
754         }
755 
756         /**
757          * Sets the icon.
758          *
759          * @param icon The icon that represents this TV input.
760          * @return This Builder object to allow for chaining of calls to builder methods.
761          * @hide
762          */
763         @SystemApi
setIcon(Icon icon)764         public Builder setIcon(Icon icon) {
765             this.mIcon = icon;
766             return this;
767         }
768 
769         /**
770          * Sets the icon for a given input state.
771          *
772          * @param icon The icon that represents this TV input for the given state.
773          * @param state The input state. Should be one of the followings.
774          *              {@link TvInputManager#INPUT_STATE_CONNECTED},
775          *              {@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} and
776          *              {@link TvInputManager#INPUT_STATE_DISCONNECTED}.
777          * @return This Builder object to allow for chaining of calls to builder methods.
778          * @hide
779          */
780         @SystemApi
setIcon(Icon icon, int state)781         public Builder setIcon(Icon icon, int state) {
782             if (state == TvInputManager.INPUT_STATE_CONNECTED) {
783                 this.mIcon = icon;
784             } else if (state == TvInputManager.INPUT_STATE_CONNECTED_STANDBY) {
785                 this.mIconStandby = icon;
786             } else if (state == TvInputManager.INPUT_STATE_DISCONNECTED) {
787                 this.mIconDisconnected = icon;
788             } else {
789                 throw new IllegalArgumentException("Unknown state: " + state);
790             }
791             return this;
792         }
793 
794         /**
795          * Sets the label.
796          *
797          * @param label The text to be used as label.
798          * @return This Builder object to allow for chaining of calls to builder methods.
799          * @hide
800          */
801         @SystemApi
setLabel(CharSequence label)802         public Builder setLabel(CharSequence label) {
803             if (mLabelResId != 0) {
804                 throw new IllegalStateException("Resource ID for label is already set.");
805             }
806             this.mLabel = label;
807             return this;
808         }
809 
810         /**
811          * Sets the label.
812          *
813          * @param resId The resource ID of the text to use.
814          * @return This Builder object to allow for chaining of calls to builder methods.
815          * @hide
816          */
817         @SystemApi
setLabel(@tringRes int resId)818         public Builder setLabel(@StringRes int resId) {
819             if (mLabel != null) {
820                 throw new IllegalStateException("Label text is already set.");
821             }
822             this.mLabelResId = resId;
823             return this;
824         }
825 
826         /**
827          * Sets the HdmiDeviceInfo.
828          *
829          * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
830          * @return This Builder object to allow for chaining of calls to builder methods.
831          * @hide
832          */
833         @SystemApi
setHdmiDeviceInfo(HdmiDeviceInfo hdmiDeviceInfo)834         public Builder setHdmiDeviceInfo(HdmiDeviceInfo hdmiDeviceInfo) {
835             if (mTvInputHardwareInfo != null) {
836                 Log.w(TAG, "TvInputHardwareInfo will not be used to build this TvInputInfo");
837                 mTvInputHardwareInfo = null;
838             }
839             this.mHdmiDeviceInfo = hdmiDeviceInfo;
840             return this;
841         }
842 
843         /**
844          * Sets the parent ID.
845          *
846          * @param parentId The parent ID.
847          * @return This Builder object to allow for chaining of calls to builder methods.
848          * @hide
849          */
850         @SystemApi
setParentId(String parentId)851         public Builder setParentId(String parentId) {
852             this.mParentId = parentId;
853             return this;
854         }
855 
856         /**
857          * Sets the TvInputHardwareInfo.
858          *
859          * @param tvInputHardwareInfo
860          * @return This Builder object to allow for chaining of calls to builder methods.
861          * @hide
862          */
863         @SystemApi
setTvInputHardwareInfo(TvInputHardwareInfo tvInputHardwareInfo)864         public Builder setTvInputHardwareInfo(TvInputHardwareInfo tvInputHardwareInfo) {
865             if (mHdmiDeviceInfo != null) {
866                 Log.w(TAG, "mHdmiDeviceInfo will not be used to build this TvInputInfo");
867                 mHdmiDeviceInfo = null;
868             }
869             this.mTvInputHardwareInfo = tvInputHardwareInfo;
870             return this;
871         }
872 
873         /**
874          * Sets the tuner count. Valid only for {@link #TYPE_TUNER}.
875          *
876          * @param tunerCount The number of tuners this TV input has.
877          * @return This Builder object to allow for chaining of calls to builder methods.
878          */
setTunerCount(int tunerCount)879         public Builder setTunerCount(int tunerCount) {
880             this.mTunerCount = tunerCount;
881             return this;
882         }
883 
884         /**
885          * Sets whether this TV input can record TV programs or not.
886          *
887          * @param canRecord Whether this TV input can record TV programs.
888          * @return This Builder object to allow for chaining of calls to builder methods.
889          */
setCanRecord(boolean canRecord)890         public Builder setCanRecord(boolean canRecord) {
891             this.mCanRecord = canRecord;
892             return this;
893         }
894 
895         /**
896          * Sets whether this TV input can pause recording TV programs or not.
897          *
898          * @param canPauseRecording Whether this TV input can pause recording TV programs.
899          * @return This Builder object to allow for chaining of calls to builder methods.
900          */
901         @NonNull
setCanPauseRecording(boolean canPauseRecording)902         public Builder setCanPauseRecording(boolean canPauseRecording) {
903             this.mCanPauseRecording = canPauseRecording;
904             return this;
905         }
906 
907         /**
908          * Sets domain-specific extras associated with this TV input.
909          *
910          * @param extras Domain-specific extras associated with this TV input. Keys <em>must</em> be
911          *            a scoped name, i.e. prefixed with a package name you own, so that different
912          *            developers will not create conflicting keys.
913          * @return This Builder object to allow for chaining of calls to builder methods.
914          */
setExtras(Bundle extras)915         public Builder setExtras(Bundle extras) {
916             this.mExtras = extras;
917             return this;
918         }
919 
920         /**
921          * Creates a {@link TvInputInfo} instance with the specified fields. Most of the information
922          * is obtained by parsing the AndroidManifest and {@link TvInputService#SERVICE_META_DATA}
923          * for the {@link TvInputService} this TV input implements.
924          *
925          * @return TvInputInfo containing information about this TV input.
926          */
build()927         public TvInputInfo build() {
928             ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName,
929                     mResolveInfo.serviceInfo.name);
930             String id;
931             int type;
932             boolean isHardwareInput = false;
933             boolean isConnectedToHdmiSwitch = false;
934             @HdmiAddressRelativePosition
935             int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
936 
937             if (mHdmiDeviceInfo != null) {
938                 id = generateInputId(componentName, mHdmiDeviceInfo);
939                 type = TYPE_HDMI;
940                 isHardwareInput = true;
941                 hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
942                 isConnectedToHdmiSwitch =
943                         hdmiConnectionRelativePosition
944                                 != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
945             } else if (mTvInputHardwareInfo != null) {
946                 id = generateInputId(componentName, mTvInputHardwareInfo);
947                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
948                 isHardwareInput = true;
949                 if (mTvInputHardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
950                     mHdmiDeviceInfo = HdmiDeviceInfo.hardwarePort(
951                             HdmiDeviceInfo.PATH_INVALID, mTvInputHardwareInfo.getHdmiPortId());
952                 }
953             } else {
954                 id = generateInputId(componentName);
955                 type = TYPE_TUNER;
956             }
957             parseServiceMetadata(type);
958             return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
959                     mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
960                     mCanRecord == null ? false : mCanRecord,
961                     mCanPauseRecording == null ? false : mCanPauseRecording,
962                     mTunerCount == null ? 0 : mTunerCount,
963                     mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
964                     mParentId, mExtras);
965         }
966 
generateInputId(ComponentName name)967         private static String generateInputId(ComponentName name) {
968             return name.flattenToShortString();
969         }
970 
generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo)971         private static String generateInputId(ComponentName name, HdmiDeviceInfo hdmiDeviceInfo) {
972             // Example of the format : "/HDMI%04X%02X"
973             String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE
974                     + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X"
975                     + "%0" + LENGTH_HDMI_DEVICE_ID + "X";
976             return name.flattenToShortString() + String.format(Locale.ENGLISH, format,
977                     hdmiDeviceInfo.getPhysicalAddress(), hdmiDeviceInfo.getId());
978         }
979 
generateInputId(ComponentName name, TvInputHardwareInfo tvInputHardwareInfo)980         private static String generateInputId(ComponentName name,
981                 TvInputHardwareInfo tvInputHardwareInfo) {
982             return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE
983                     + tvInputHardwareInfo.getDeviceId();
984         }
985 
getRelativePosition(Context context, HdmiDeviceInfo info)986         private static int getRelativePosition(Context context, HdmiDeviceInfo info) {
987             HdmiControlManager hcm =
988                     (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE);
989             if (hcm == null) {
990                 return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
991             }
992             return HdmiUtils.getHdmiAddressRelativePosition(
993                     info.getPhysicalAddress(), hcm.getPhysicalAddress());
994         }
995 
parseServiceMetadata(int inputType)996         private void parseServiceMetadata(int inputType) {
997             ServiceInfo si = mResolveInfo.serviceInfo;
998             PackageManager pm = mContext.getPackageManager();
999             try (XmlResourceParser parser =
1000                          si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA)) {
1001                 if (parser == null) {
1002                     throw new IllegalStateException("No " + TvInputService.SERVICE_META_DATA
1003                             + " meta-data found for " + si.name);
1004                 }
1005 
1006                 Resources res = pm.getResourcesForApplication(si.applicationInfo);
1007                 AttributeSet attrs = Xml.asAttributeSet(parser);
1008 
1009                 int type;
1010                 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1011                         && type != XmlPullParser.START_TAG) {
1012                 }
1013 
1014                 String nodeName = parser.getName();
1015                 if (!XML_START_TAG_NAME.equals(nodeName)) {
1016                     throw new IllegalStateException("Meta-data does not start with "
1017                             + XML_START_TAG_NAME + " tag for " + si.name);
1018                 }
1019 
1020                 TypedArray sa = res.obtainAttributes(attrs,
1021                         com.android.internal.R.styleable.TvInputService);
1022                 mSetupActivity = sa.getString(
1023                         com.android.internal.R.styleable.TvInputService_setupActivity);
1024                 if (mCanRecord == null) {
1025                     mCanRecord = sa.getBoolean(
1026                             com.android.internal.R.styleable.TvInputService_canRecord, false);
1027                 }
1028                 if (mTunerCount == null && inputType == TYPE_TUNER) {
1029                     mTunerCount = sa.getInt(
1030                             com.android.internal.R.styleable.TvInputService_tunerCount, 1);
1031                 }
1032                 if (mCanPauseRecording == null) {
1033                     mCanPauseRecording = sa.getBoolean(
1034                             com.android.internal.R.styleable.TvInputService_canPauseRecording,
1035                             false);
1036                 }
1037 
1038                 sa.recycle();
1039             } catch (IOException | XmlPullParserException e) {
1040                 throw new IllegalStateException("Failed reading meta-data for " + si.packageName, e);
1041             } catch (NameNotFoundException e) {
1042                 throw new IllegalStateException("No resources found for " + si.packageName, e);
1043             }
1044         }
1045     }
1046 
1047     /**
1048      * Utility class for putting and getting settings for TV input.
1049      *
1050      * @hide
1051      */
1052     @SystemApi
1053     public static final class TvInputSettings {
1054         private static final String TV_INPUT_SEPARATOR = ":";
1055         private static final String CUSTOM_NAME_SEPARATOR = ",";
1056 
TvInputSettings()1057         private TvInputSettings() { }
1058 
isHidden(Context context, String inputId, int userId)1059         private static boolean isHidden(Context context, String inputId, int userId) {
1060             return getHiddenTvInputIds(context, userId).contains(inputId);
1061         }
1062 
getCustomLabel(Context context, String inputId, int userId)1063         private static String getCustomLabel(Context context, String inputId, int userId) {
1064             return getCustomLabels(context, userId).get(inputId);
1065         }
1066 
1067         /**
1068          * Returns a set of TV input IDs which are marked as hidden by user in the settings.
1069          *
1070          * @param context The application context
1071          * @param userId The user ID for the stored hidden input set
1072          * @hide
1073          */
1074         @SystemApi
getHiddenTvInputIds(Context context, int userId)1075         public static Set<String> getHiddenTvInputIds(Context context, int userId) {
1076             String hiddenIdsString = Settings.Secure.getStringForUser(
1077                     context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId);
1078             Set<String> set = new HashSet<>();
1079             if (TextUtils.isEmpty(hiddenIdsString)) {
1080                 return set;
1081             }
1082             String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR);
1083             for (String id : ids) {
1084                 set.add(Uri.decode(id));
1085             }
1086             return set;
1087         }
1088 
1089         /**
1090          * Returns a map of TV input ID/custom label pairs set by the user in the settings.
1091          *
1092          * @param context The application context
1093          * @param userId The user ID for the stored hidden input map
1094          * @hide
1095          */
1096         @SystemApi
getCustomLabels(Context context, int userId)1097         public static Map<String, String> getCustomLabels(Context context, int userId) {
1098             String labelsString = Settings.Secure.getStringForUser(
1099                     context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId);
1100             Map<String, String> map = new HashMap<>();
1101             if (TextUtils.isEmpty(labelsString)) {
1102                 return map;
1103             }
1104             String[] pairs = labelsString.split(TV_INPUT_SEPARATOR);
1105             for (String pairString : pairs) {
1106                 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR);
1107                 map.put(Uri.decode(pair[0]), Uri.decode(pair[1]));
1108             }
1109             return map;
1110         }
1111 
1112         /**
1113          * Stores a set of TV input IDs which are marked as hidden by user. This is expected to
1114          * be called from the settings app.
1115          *
1116          * @param context The application context
1117          * @param hiddenInputIds A set including all the hidden TV input IDs
1118          * @param userId The user ID for the stored hidden input set
1119          * @hide
1120          */
1121         @SystemApi
putHiddenTvInputs(Context context, Set<String> hiddenInputIds, int userId)1122         public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds,
1123                 int userId) {
1124             StringBuilder builder = new StringBuilder();
1125             boolean firstItem = true;
1126             for (String inputId : hiddenInputIds) {
1127                 ensureValidField(inputId);
1128                 if (firstItem) {
1129                     firstItem = false;
1130                 } else {
1131                     builder.append(TV_INPUT_SEPARATOR);
1132                 }
1133                 builder.append(Uri.encode(inputId));
1134             }
1135             Settings.Secure.putStringForUser(context.getContentResolver(),
1136                     Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId);
1137 
1138             // Notify of the TvInputInfo changes.
1139             TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
1140             for (String inputId : hiddenInputIds) {
1141                 TvInputInfo info = tm.getTvInputInfo(inputId);
1142                 if (info != null) {
1143                     tm.updateTvInputInfo(info);
1144                 }
1145             }
1146         }
1147 
1148         /**
1149          * Stores a map of TV input ID/custom label set by user. This is expected to be
1150          * called from the settings app.
1151          *
1152          * @param context The application context.
1153          * @param customLabels A map of TV input ID/custom label pairs
1154          * @param userId The user ID for the stored hidden input map
1155          * @hide
1156          */
1157         @SystemApi
putCustomLabels(Context context, Map<String, String> customLabels, int userId)1158         public static void putCustomLabels(Context context,
1159                 Map<String, String> customLabels, int userId) {
1160             StringBuilder builder = new StringBuilder();
1161             boolean firstItem = true;
1162             for (Map.Entry<String, String> entry: customLabels.entrySet()) {
1163                 ensureValidField(entry.getKey());
1164                 ensureValidField(entry.getValue());
1165                 if (firstItem) {
1166                     firstItem = false;
1167                 } else {
1168                     builder.append(TV_INPUT_SEPARATOR);
1169                 }
1170                 builder.append(Uri.encode(entry.getKey()));
1171                 builder.append(CUSTOM_NAME_SEPARATOR);
1172                 builder.append(Uri.encode(entry.getValue()));
1173             }
1174             Settings.Secure.putStringForUser(context.getContentResolver(),
1175                     Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId);
1176 
1177             // Notify of the TvInputInfo changes.
1178             TvInputManager tm = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
1179             for (String inputId : customLabels.keySet()) {
1180                 TvInputInfo info = tm.getTvInputInfo(inputId);
1181                 if (info != null) {
1182                     tm.updateTvInputInfo(info);
1183                 }
1184             }
1185         }
1186 
ensureValidField(String value)1187         private static void ensureValidField(String value) {
1188             if (TextUtils.isEmpty(value)) {
1189                 throw new IllegalArgumentException(value + " should not empty ");
1190             }
1191         }
1192     }
1193 }
1194