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