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