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