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.NonNull;
     20 import android.annotation.SystemApi;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageManager.NameNotFoundException;
     26 import android.content.pm.ResolveInfo;
     27 import android.content.pm.ServiceInfo;
     28 import android.content.res.Resources;
     29 import android.content.res.TypedArray;
     30 import android.content.res.XmlResourceParser;
     31 import android.graphics.drawable.Drawable;
     32 import android.graphics.drawable.Icon;
     33 import android.hardware.hdmi.HdmiDeviceInfo;
     34 import android.net.Uri;
     35 import android.os.Parcel;
     36 import android.os.Parcelable;
     37 import android.os.UserHandle;
     38 import android.provider.Settings;
     39 import android.text.TextUtils;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.util.SparseIntArray;
     43 import android.util.Xml;
     44 
     45 import org.xmlpull.v1.XmlPullParser;
     46 import org.xmlpull.v1.XmlPullParserException;
     47 
     48 import java.io.IOException;
     49 import java.io.InputStream;
     50 import java.util.HashMap;
     51 import java.util.HashSet;
     52 import java.util.Locale;
     53 import java.util.Map;
     54 import java.util.Set;
     55 
     56 /**
     57  * This class is used to specify meta information of a TV input.
     58  */
     59 public final class TvInputInfo implements Parcelable {
     60     private static final boolean DEBUG = false;
     61     private static final String TAG = "TvInputInfo";
     62 
     63     // Should be in sync with frameworks/base/core/res/res/values/attrs.xml
     64     /**
     65      * TV input type: the TV input service is a tuner which provides channels.
     66      */
     67     public static final int TYPE_TUNER = 0;
     68     /**
     69      * TV input type: a generic hardware TV input type.
     70      */
     71     public static final int TYPE_OTHER = 1000;
     72     /**
     73      * TV input type: the TV input service represents a composite port.
     74      */
     75     public static final int TYPE_COMPOSITE = 1001;
     76     /**
     77      * TV input type: the TV input service represents a SVIDEO port.
     78      */
     79     public static final int TYPE_SVIDEO = 1002;
     80     /**
     81      * TV input type: the TV input service represents a SCART port.
     82      */
     83     public static final int TYPE_SCART = 1003;
     84     /**
     85      * TV input type: the TV input service represents a component port.
     86      */
     87     public static final int TYPE_COMPONENT = 1004;
     88     /**
     89      * TV input type: the TV input service represents a VGA port.
     90      */
     91     public static final int TYPE_VGA = 1005;
     92     /**
     93      * TV input type: the TV input service represents a DVI port.
     94      */
     95     public static final int TYPE_DVI = 1006;
     96     /**
     97      * TV input type: the TV input service is HDMI. (e.g. HDMI 1)
     98      */
     99     public static final int TYPE_HDMI = 1007;
    100     /**
    101      * TV input type: the TV input service represents a display port.
    102      */
    103     public static final int TYPE_DISPLAY_PORT = 1008;
    104 
    105     /**
    106      * The ID of the TV input to provide to the setup activity and settings activity.
    107      */
    108     public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID";
    109 
    110     private static final SparseIntArray sHardwareTypeToTvInputType = new SparseIntArray();
    111 
    112     private static final String XML_START_TAG_NAME = "tv-input";
    113     private static final String DELIMITER_INFO_IN_ID = "/";
    114     private static final String PREFIX_HDMI_DEVICE = "HDMI";
    115     private static final String PREFIX_HARDWARE_DEVICE = "HW";
    116     private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4;
    117     private static final int LENGTH_HDMI_DEVICE_ID = 2;
    118 
    119     private final ResolveInfo mService;
    120     private final String mId;
    121     private final String mParentId;
    122     private final int mType;
    123     private final boolean mIsHardwareInput;
    124 
    125     // Attributes from XML meta data.
    126     private String mSetupActivity;
    127     private String mSettingsActivity;
    128 
    129     private HdmiDeviceInfo mHdmiDeviceInfo;
    130     private int mLabelRes;
    131     private String mLabel;
    132     private Icon mIcon;
    133     private Uri mIconUri;
    134     private boolean mIsConnectedToHdmiSwitch;
    135 
    136     static {
    137         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE,
    138                 TYPE_OTHER);
    139         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER);
    140         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, TYPE_COMPOSITE);
    141         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO);
    142         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART);
    143         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, TYPE_COMPONENT);
    144         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA);
    145         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI);
    146         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI);
    147         sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT,
    148                 TYPE_DISPLAY_PORT);
    149     }
    150 
    151     /**
    152      * Create a new instance of the TvInputInfo class,
    153      * instantiating it from the given Context and ResolveInfo.
    154      *
    155      * @param service The ResolveInfo returned from the package manager about this TV input service.
    156      * @hide
    157      */
    158     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
    159             throws XmlPullParserException, IOException {
    160         return createTvInputInfo(context, service, generateInputIdForComponentName(
    161                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)),
    162                 null, TYPE_TUNER, false, 0, null, null, null, false);
    163     }
    164 
    165     /**
    166      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
    167      * ResolveInfo, and HdmiDeviceInfo.
    168      *
    169      * @param service The ResolveInfo returned from the package manager about this TV input service.
    170      * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
    171      * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
    172      * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
    173      *            label will be loaded.
    174      * @param iconUri The {@link android.net.Uri} to load the icon image. See
    175      *            {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
    176      *            the application icon of {@code service} will be loaded.
    177      * @hide
    178      */
    179     @SystemApi
    180     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
    181             HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri)
    182                     throws XmlPullParserException, IOException {
    183         boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
    184         TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice(
    185                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
    186                 hdmiDeviceInfo), parentId, TYPE_HDMI, true, 0, label, null, iconUri,
    187                 isConnectedToHdmiSwitch);
    188         input.mHdmiDeviceInfo = hdmiDeviceInfo;
    189         return input;
    190     }
    191 
    192     /**
    193      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
    194      * ResolveInfo, and HdmiDeviceInfo.
    195      *
    196      * @param service The ResolveInfo returned from the package manager about this TV input service.
    197      * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
    198      * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
    199      * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
    200      *            {@code service} label will be loaded.
    201      * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
    202      *            {@code null}, the application icon of {@code service} will be loaded.
    203      * @hide
    204      */
    205     @SystemApi
    206     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
    207             HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon)
    208             throws XmlPullParserException, IOException {
    209         boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
    210         TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice(
    211                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
    212                 hdmiDeviceInfo), parentId, TYPE_HDMI, true, labelRes, null, icon, null,
    213                 isConnectedToHdmiSwitch);
    214         input.mHdmiDeviceInfo = hdmiDeviceInfo;
    215         return input;
    216     }
    217 
    218     /**
    219      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
    220      * ResolveInfo, and TvInputHardwareInfo.
    221      *
    222      * @param service The ResolveInfo returned from the package manager about this TV input service.
    223      * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
    224      * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
    225      *            label will be loaded.
    226      * @param iconUri The {@link android.net.Uri} to load the icon image. See
    227      *            {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
    228      *            the application icon of {@code service} will be loaded.
    229      * @hide
    230      */
    231     @SystemApi
    232     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
    233             TvInputHardwareInfo hardwareInfo, String label, Uri iconUri)
    234                     throws XmlPullParserException, IOException {
    235         int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER);
    236         return createTvInputInfo(context, service, generateInputIdForHardware(
    237                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
    238                 hardwareInfo), null, inputType, true, 0, label, null, iconUri, false);
    239     }
    240 
    241     /**
    242      * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
    243      * ResolveInfo, and TvInputHardwareInfo.
    244      *
    245      * @param service The ResolveInfo returned from the package manager about this TV input service.
    246      * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
    247      * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0},
    248      *            {@code service} label will be loaded.
    249      * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is
    250      *            {@code null}, the application icon of {@code service} will be loaded.
    251      * @hide
    252      */
    253     @SystemApi
    254     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
    255             TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon)
    256             throws XmlPullParserException, IOException {
    257         int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER);
    258         return createTvInputInfo(context, service, generateInputIdForHardware(
    259                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
    260                 hardwareInfo), null, inputType, true, labelRes, null, icon, null, false);
    261     }
    262 
    263     private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, String id,
    264             String parentId, int inputType, boolean isHardwareInput, int labelRes, String label,
    265             Icon icon, Uri iconUri, boolean isConnectedToHdmiSwitch)
    266                     throws XmlPullParserException, IOException {
    267         ServiceInfo si = service.serviceInfo;
    268         PackageManager pm = context.getPackageManager();
    269         XmlResourceParser parser = null;
    270         try {
    271             parser = si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA);
    272             if (parser == null) {
    273                 throw new XmlPullParserException("No " + TvInputService.SERVICE_META_DATA
    274                         + " meta-data for " + si.name);
    275             }
    276 
    277             Resources res = pm.getResourcesForApplication(si.applicationInfo);
    278             AttributeSet attrs = Xml.asAttributeSet(parser);
    279 
    280             int type;
    281             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    282                     && type != XmlPullParser.START_TAG) {
    283             }
    284 
    285             String nodeName = parser.getName();
    286             if (!XML_START_TAG_NAME.equals(nodeName)) {
    287                 throw new XmlPullParserException(
    288                         "Meta-data does not start with tv-input-service tag in " + si.name);
    289             }
    290 
    291             TvInputInfo input = new TvInputInfo(service, id, parentId, inputType, isHardwareInput);
    292             TypedArray sa = res.obtainAttributes(attrs,
    293                     com.android.internal.R.styleable.TvInputService);
    294             input.mSetupActivity = sa.getString(
    295                     com.android.internal.R.styleable.TvInputService_setupActivity);
    296             if (DEBUG) {
    297                 Log.d(TAG, "Setup activity loaded. [" + input.mSetupActivity + "] for " + si.name);
    298             }
    299             if (inputType == TYPE_TUNER && TextUtils.isEmpty(input.mSetupActivity)) {
    300                 throw new XmlPullParserException("Setup activity not found in " + si.name);
    301             }
    302             input.mSettingsActivity = sa.getString(
    303                     com.android.internal.R.styleable.TvInputService_settingsActivity);
    304             if (DEBUG) {
    305                 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
    306                         + si.name);
    307             }
    308             sa.recycle();
    309 
    310             input.mLabelRes = labelRes;
    311             input.mLabel = label;
    312             input.mIcon = icon;
    313             input.mIconUri = iconUri;
    314             input.mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
    315             return input;
    316         } catch (NameNotFoundException e) {
    317             throw new XmlPullParserException("Unable to create context for: " + si.packageName);
    318         } finally {
    319             if (parser != null) {
    320                 parser.close();
    321             }
    322         }
    323     }
    324 
    325     /**
    326      * Constructor.
    327      *
    328      * @param service The ResolveInfo returned from the package manager about this TV input service.
    329      * @param id ID of this TV input. Should be generated via generateInputId*().
    330      * @param parentId ID of this TV input's parent input. {@code null} if none exists.
    331      * @param type The type of this TV input service.
    332      * @param isHardwareInput {@code true} if this TV input represents a hardware device.
    333      *         {@code false} otherwise.
    334      */
    335     private TvInputInfo(ResolveInfo service, String id, String parentId, int type,
    336             boolean isHardwareInput) {
    337         mService = service;
    338         mId = id;
    339         mParentId = parentId;
    340         mType = type;
    341         mIsHardwareInput = isHardwareInput;
    342     }
    343 
    344     /**
    345      * Returns a unique ID for this TV input. The ID is generated from the package and class name
    346      * implementing the TV input service.
    347      */
    348     public String getId() {
    349         return mId;
    350     }
    351 
    352     /**
    353      * Returns the parent input ID.
    354      *
    355      * <p>A TV input may have a parent input if the TV input is actually a logical representation of
    356      * a device behind the hardware port represented by the parent input.
    357      * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV
    358      * input. In this case, the parent input of this logical device is the HDMI port.
    359      *
    360      * <p>Applications may group inputs by parent input ID to provide an easier access to inputs
    361      * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind
    362      * the same HDMI port have the same parent ID, which is the ID representing the port. Thus
    363      * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it
    364      * together using this method.
    365      *
    366      * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is
    367      *         not specified.
    368      */
    369     public String getParentId() {
    370         return mParentId;
    371     }
    372 
    373     /**
    374      * Returns the information of the service that implements this TV input.
    375      */
    376     public ServiceInfo getServiceInfo() {
    377         return mService.serviceInfo;
    378     }
    379 
    380     /**
    381      * Returns the component of the service that implements this TV input.
    382      * @hide
    383      */
    384     public ComponentName getComponent() {
    385         return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
    386     }
    387 
    388     /**
    389      * Returns an intent to start the setup activity for this TV input.
    390      */
    391     public Intent createSetupIntent() {
    392         if (!TextUtils.isEmpty(mSetupActivity)) {
    393             Intent intent = new Intent(Intent.ACTION_MAIN);
    394             intent.setClassName(mService.serviceInfo.packageName, mSetupActivity);
    395             intent.putExtra(EXTRA_INPUT_ID, getId());
    396             return intent;
    397         }
    398         return null;
    399     }
    400 
    401     /**
    402      * Returns an intent to start the settings activity for this TV input.
    403      */
    404     public Intent createSettingsIntent() {
    405         if (!TextUtils.isEmpty(mSettingsActivity)) {
    406             Intent intent = new Intent(Intent.ACTION_MAIN);
    407             intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity);
    408             intent.putExtra(EXTRA_INPUT_ID, getId());
    409             return intent;
    410         }
    411         return null;
    412     }
    413 
    414     /**
    415      * Returns the type of this TV input.
    416      */
    417     public int getType() {
    418         return mType;
    419     }
    420 
    421     /**
    422      * Returns the HDMI device information of this TV input.
    423      * @hide
    424      */
    425     @SystemApi
    426     public HdmiDeviceInfo getHdmiDeviceInfo() {
    427         if (mType == TYPE_HDMI) {
    428             return mHdmiDeviceInfo;
    429         }
    430         return null;
    431     }
    432 
    433     /**
    434      * Returns {@code true} if this TV input is pass-though which does not have any real channels in
    435      * TvProvider. {@code false} otherwise.
    436      *
    437      * @see TvContract#buildChannelUriForPassthroughInput(String)
    438      */
    439     public boolean isPassthroughInput() {
    440         return mType != TYPE_TUNER;
    441     }
    442 
    443     /**
    444      * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner,
    445      * HDMI1) {@code false} otherwise.
    446      * @hide
    447      */
    448     @SystemApi
    449     public boolean isHardwareInput() {
    450         return mIsHardwareInput;
    451     }
    452 
    453     /**
    454      * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
    455      * the device isn't directly connected to a HDMI port.
    456      * @hide
    457      */
    458     @SystemApi
    459     public boolean isConnectedToHdmiSwitch() {
    460         return mIsConnectedToHdmiSwitch;
    461     }
    462 
    463     /**
    464      * Checks if this TV input is marked hidden by the user in the settings.
    465      *
    466      * @param context Supplies a {@link Context} used to check if this TV input is hidden.
    467      * @return {@code true} if the user marked this TV input hidden in settings. {@code false}
    468      *         otherwise.
    469      * @hide
    470      */
    471     @SystemApi
    472     public boolean isHidden(Context context) {
    473         return TvInputSettings.isHidden(context, mId, UserHandle.myUserId());
    474     }
    475 
    476     /**
    477      * Loads the user-displayed label for this TV input.
    478      *
    479      * @param context Supplies a {@link Context} used to load the label.
    480      * @return a CharSequence containing the TV input's label. If the TV input does not have
    481      *         a label, its name is returned.
    482      */
    483     public CharSequence loadLabel(@NonNull Context context) {
    484         if (mLabelRes != 0) {
    485             return context.getPackageManager().getText(mService.serviceInfo.packageName, mLabelRes,
    486                     null);
    487         } else if (!TextUtils.isEmpty(mLabel)) {
    488             return mLabel;
    489         }
    490         return mService.loadLabel(context.getPackageManager());
    491     }
    492 
    493     /**
    494      * Loads the custom label set by user in settings.
    495      *
    496      * @param context Supplies a {@link Context} used to load the custom label.
    497      * @return a CharSequence containing the TV input's custom label. {@code null} if there is no
    498      *         custom label.
    499      * @hide
    500      */
    501     @SystemApi
    502     public CharSequence loadCustomLabel(Context context) {
    503         return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId());
    504     }
    505 
    506     /**
    507      * Loads the user-displayed icon for this TV input.
    508      *
    509      * @param context Supplies a {@link Context} used to load the icon.
    510      * @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
    511      *         application's icon is returned. If it's unavailable too, {@code null} is returned.
    512      */
    513     public Drawable loadIcon(@NonNull Context context) {
    514         if (mIcon != null) {
    515             return mIcon.loadDrawable(context);
    516         } else if (mIconUri != null) {
    517             try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
    518                 Drawable drawable = Drawable.createFromStream(is, null);
    519                 if (drawable != null) {
    520                     return drawable;
    521                 }
    522             } catch (IOException e) {
    523                 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
    524                 // Falls back.
    525             }
    526         }
    527         return loadServiceIcon(context);
    528     }
    529 
    530     @Override
    531     public int describeContents() {
    532         return 0;
    533     }
    534 
    535     @Override
    536     public int hashCode() {
    537         return mId.hashCode();
    538     }
    539 
    540     @Override
    541     public boolean equals(Object o) {
    542         if (o == this) {
    543             return true;
    544         }
    545 
    546         if (!(o instanceof TvInputInfo)) {
    547             return false;
    548         }
    549 
    550         TvInputInfo obj = (TvInputInfo) o;
    551         return mId.equals(obj.mId);
    552     }
    553 
    554     @Override
    555     public String toString() {
    556         return "TvInputInfo{id=" + mId
    557                 + ", pkg=" + mService.serviceInfo.packageName
    558                 + ", service=" + mService.serviceInfo.name + "}";
    559     }
    560 
    561     /**
    562      * Used to package this object into a {@link Parcel}.
    563      *
    564      * @param dest The {@link Parcel} to be written.
    565      * @param flags The flags used for parceling.
    566      */
    567     @Override
    568     public void writeToParcel(@NonNull Parcel dest, int flags) {
    569         dest.writeString(mId);
    570         dest.writeString(mParentId);
    571         mService.writeToParcel(dest, flags);
    572         dest.writeString(mSetupActivity);
    573         dest.writeString(mSettingsActivity);
    574         dest.writeInt(mType);
    575         dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
    576         dest.writeParcelable(mHdmiDeviceInfo, flags);
    577         dest.writeParcelable(mIcon, flags);
    578         dest.writeParcelable(mIconUri, flags);
    579         dest.writeInt(mLabelRes);
    580         dest.writeString(mLabel);
    581         dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
    582     }
    583 
    584     private Drawable loadServiceIcon(Context context) {
    585         if (mService.serviceInfo.icon == 0
    586                 && mService.serviceInfo.applicationInfo.icon == 0) {
    587             return null;
    588         }
    589         return mService.serviceInfo.loadIcon(context.getPackageManager());
    590     }
    591 
    592     /**
    593      * Used to generate an input id from a ComponentName.
    594      *
    595      * @param name the component name for generating an input id.
    596      * @return the generated input id for the given {@code name}.
    597      */
    598     private static String generateInputIdForComponentName(ComponentName name) {
    599         return name.flattenToShortString();
    600     }
    601 
    602     /**
    603      * Used to generate an input id from a ComponentName and HdmiDeviceInfo.
    604      *
    605      * @param name the component name for generating an input id.
    606      * @param deviceInfo HdmiDeviceInfo describing this TV input.
    607      * @return the generated input id for the given {@code name} and {@code deviceInfo}.
    608      */
    609     private static String generateInputIdForHdmiDevice(
    610             ComponentName name, HdmiDeviceInfo deviceInfo) {
    611         // Example of the format : "/HDMI%04X%02X"
    612         String format = DELIMITER_INFO_IN_ID + PREFIX_HDMI_DEVICE
    613                 + "%0" + LENGTH_HDMI_PHYSICAL_ADDRESS + "X"
    614                 + "%0" + LENGTH_HDMI_DEVICE_ID + "X";
    615         return name.flattenToShortString() + String.format(Locale.ENGLISH, format,
    616                 deviceInfo.getPhysicalAddress(), deviceInfo.getId());
    617     }
    618 
    619     /**
    620      * Used to generate an input id from a ComponentName and TvInputHardwareInfo
    621      *
    622      * @param name the component name for generating an input id.
    623      * @param hardwareInfo TvInputHardwareInfo describing this TV input.
    624      * @return the generated input id for the given {@code name} and {@code hardwareInfo}.
    625      */
    626     private static String generateInputIdForHardware(
    627             ComponentName name, TvInputHardwareInfo hardwareInfo) {
    628         return name.flattenToShortString() + DELIMITER_INFO_IN_ID + PREFIX_HARDWARE_DEVICE
    629                 + hardwareInfo.getDeviceId();
    630     }
    631 
    632     public static final Parcelable.Creator<TvInputInfo> CREATOR =
    633             new Parcelable.Creator<TvInputInfo>() {
    634         @Override
    635         public TvInputInfo createFromParcel(Parcel in) {
    636             return new TvInputInfo(in);
    637         }
    638 
    639         @Override
    640         public TvInputInfo[] newArray(int size) {
    641             return new TvInputInfo[size];
    642         }
    643     };
    644 
    645     private TvInputInfo(Parcel in) {
    646         mId = in.readString();
    647         mParentId = in.readString();
    648         mService = ResolveInfo.CREATOR.createFromParcel(in);
    649         mSetupActivity = in.readString();
    650         mSettingsActivity = in.readString();
    651         mType = in.readInt();
    652         mIsHardwareInput = in.readByte() == 1;
    653         mHdmiDeviceInfo = in.readParcelable(null);
    654         mIcon = in.readParcelable(null);
    655         mIconUri = in.readParcelable(null);
    656         mLabelRes = in.readInt();
    657         mLabel = in.readString();
    658         mIsConnectedToHdmiSwitch = in.readByte() == 1;
    659     }
    660 
    661     /**
    662      * Utility class for putting and getting settings for TV input.
    663      *
    664      * @hide
    665      */
    666     @SystemApi
    667     public static final class TvInputSettings {
    668         private static final String TV_INPUT_SEPARATOR = ":";
    669         private static final String CUSTOM_NAME_SEPARATOR = ",";
    670 
    671         private TvInputSettings() { }
    672 
    673         private static boolean isHidden(Context context, String inputId, int userId) {
    674             return getHiddenTvInputIds(context, userId).contains(inputId);
    675         }
    676 
    677         private static String getCustomLabel(Context context, String inputId, int userId) {
    678             return getCustomLabels(context, userId).get(inputId);
    679         }
    680 
    681         /**
    682          * Returns a set of TV input IDs which are marked as hidden by user in the settings.
    683          *
    684          * @param context The application context
    685          * @param userId The user ID for the stored hidden input set
    686          * @hide
    687          */
    688         @SystemApi
    689         public static Set<String> getHiddenTvInputIds(Context context, int userId) {
    690             String hiddenIdsString = Settings.Secure.getStringForUser(
    691                     context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId);
    692             Set<String> set = new HashSet<>();
    693             if (TextUtils.isEmpty(hiddenIdsString)) {
    694                 return set;
    695             }
    696             String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR);
    697             for (String id : ids) {
    698                 set.add(Uri.decode(id));
    699             }
    700             return set;
    701         }
    702 
    703         /**
    704          * Returns a map of TV input ID/custom label pairs set by the user in the settings.
    705          *
    706          * @param context The application context
    707          * @param userId The user ID for the stored hidden input map
    708          * @hide
    709          */
    710         @SystemApi
    711         public static Map<String, String> getCustomLabels(Context context, int userId) {
    712             String labelsString = Settings.Secure.getStringForUser(
    713                     context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId);
    714             Map<String, String> map = new HashMap<>();
    715             if (TextUtils.isEmpty(labelsString)) {
    716                 return map;
    717             }
    718             String[] pairs = labelsString.split(TV_INPUT_SEPARATOR);
    719             for (String pairString : pairs) {
    720                 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR);
    721                 map.put(Uri.decode(pair[0]), Uri.decode(pair[1]));
    722             }
    723             return map;
    724         }
    725 
    726         /**
    727          * Stores a set of TV input IDs which are marked as hidden by user. This is expected to
    728          * be called from the settings app.
    729          *
    730          * @param context The application context
    731          * @param hiddenInputIds A set including all the hidden TV input IDs
    732          * @param userId The user ID for the stored hidden input set
    733          * @hide
    734          */
    735         @SystemApi
    736         public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds,
    737                 int userId) {
    738             StringBuilder builder = new StringBuilder();
    739             boolean firstItem = true;
    740             for (String inputId : hiddenInputIds) {
    741                 ensureValidField(inputId);
    742                 if (firstItem) {
    743                     firstItem = false;
    744                 } else {
    745                     builder.append(TV_INPUT_SEPARATOR);
    746                 }
    747                 builder.append(Uri.encode(inputId));
    748             }
    749             Settings.Secure.putStringForUser(context.getContentResolver(),
    750                     Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId);
    751         }
    752 
    753         /**
    754          * Stores a map of TV input ID/custom label set by user. This is expected to be
    755          * called from the settings app.
    756          *
    757          * @param context The application context.
    758          * @param customLabels A map of TV input ID/custom label pairs
    759          * @param userId The user ID for the stored hidden input map
    760          * @hide
    761          */
    762         @SystemApi
    763         public static void putCustomLabels(Context context,
    764                 Map<String, String> customLabels, int userId) {
    765             StringBuilder builder = new StringBuilder();
    766             boolean firstItem = true;
    767             for (Map.Entry<String, String> entry: customLabels.entrySet()) {
    768                 ensureValidField(entry.getKey());
    769                 ensureValidField(entry.getValue());
    770                 if (firstItem) {
    771                     firstItem = false;
    772                 } else {
    773                     builder.append(TV_INPUT_SEPARATOR);
    774                 }
    775                 builder.append(Uri.encode(entry.getKey()));
    776                 builder.append(CUSTOM_NAME_SEPARATOR);
    777                 builder.append(Uri.encode(entry.getValue()));
    778             }
    779             Settings.Secure.putStringForUser(context.getContentResolver(),
    780                     Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId);
    781         }
    782 
    783         private static void ensureValidField(String value) {
    784             if (TextUtils.isEmpty(value)) {
    785                 throw new IllegalArgumentException(value + " should not empty ");
    786             }
    787         }
    788     }
    789 }
    790