/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media.tv; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.StringRes; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.hdmi.HdmiDeviceInfo; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.SparseIntArray; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; /** * This class is used to specify meta information of a TV input. */ public final class TvInputInfo implements Parcelable { private static final boolean DEBUG = false; private static final String TAG = "TvInputInfo"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_TUNER, TYPE_OTHER, TYPE_COMPOSITE, TYPE_SVIDEO, TYPE_SCART, TYPE_COMPONENT, TYPE_VGA, TYPE_DVI, TYPE_HDMI, TYPE_DISPLAY_PORT}) public @interface Type {} // Should be in sync with frameworks/base/core/res/res/values/attrs.xml /** * TV input type: the TV input service is a tuner which provides channels. */ public static final int TYPE_TUNER = 0; /** * TV input type: a generic hardware TV input type. */ public static final int TYPE_OTHER = 1000; /** * TV input type: the TV input service represents a composite port. */ public static final int TYPE_COMPOSITE = 1001; /** * TV input type: the TV input service represents a SVIDEO port. */ public static final int TYPE_SVIDEO = 1002; /** * TV input type: the TV input service represents a SCART port. */ public static final int TYPE_SCART = 1003; /** * TV input type: the TV input service represents a component port. */ public static final int TYPE_COMPONENT = 1004; /** * TV input type: the TV input service represents a VGA port. */ public static final int TYPE_VGA = 1005; /** * TV input type: the TV input service represents a DVI port. */ public static final int TYPE_DVI = 1006; /** * TV input type: the TV input service is HDMI. (e.g. HDMI 1) */ public static final int TYPE_HDMI = 1007; /** * TV input type: the TV input service represents a display port. */ public static final int TYPE_DISPLAY_PORT = 1008; /** * Used as a String extra field in setup intents created by {@link #createSetupIntent()} to * supply the ID of a specific TV input to set up. */ public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID"; private final ResolveInfo mService; private final String mId; private final int mType; private final boolean mIsHardwareInput; // TODO: Remove mIconUri when createTvInputInfo() is removed. private Uri mIconUri; private final CharSequence mLabel; private final int mLabelResId; private final Icon mIcon; private final Icon mIconStandby; private final Icon mIconDisconnected; // Attributes from XML meta data. private final String mSetupActivity; private final String mSettingsActivity; private final boolean mCanRecord; private final int mTunerCount; // Attributes specific to HDMI private final HdmiDeviceInfo mHdmiDeviceInfo; private final boolean mIsConnectedToHdmiSwitch; private final String mParentId; private final Bundle mExtras; /** * Create a new instance of the TvInputInfo class, instantiating it from the given Context, * ResolveInfo, and HdmiDeviceInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. * @param parentId The ID of this TV input's parent input. {@code null} if none exists. * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} * label will be loaded. * @param iconUri The {@link android.net.Uri} to load the icon image. See * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, * the application icon of {@code service} will be loaded. * @hide * @deprecated Use {@link Builder} instead. */ @Deprecated @SystemApi public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri) throws XmlPullParserException, IOException { TvInputInfo info = new TvInputInfo.Builder(context, service) .setHdmiDeviceInfo(hdmiDeviceInfo) .setParentId(parentId) .setLabel(label) .build(); info.mIconUri = iconUri; return info; } /** * Create a new instance of the TvInputInfo class, instantiating it from the given Context, * ResolveInfo, and HdmiDeviceInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. * @param parentId The ID of this TV input's parent input. {@code null} if none exists. * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, * {@code service} label will be loaded. * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is * {@code null}, the application icon of {@code service} will be loaded. * @hide * @deprecated Use {@link Builder} instead. */ @Deprecated @SystemApi public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, HdmiDeviceInfo hdmiDeviceInfo, String parentId, int labelRes, Icon icon) throws XmlPullParserException, IOException { return new TvInputInfo.Builder(context, service) .setHdmiDeviceInfo(hdmiDeviceInfo) .setParentId(parentId) .setLabel(labelRes) .setIcon(icon) .build(); } /** * Create a new instance of the TvInputInfo class, instantiating it from the given Context, * ResolveInfo, and TvInputHardwareInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} * label will be loaded. * @param iconUri The {@link android.net.Uri} to load the icon image. See * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, * the application icon of {@code service} will be loaded. * @hide * @deprecated Use {@link Builder} instead. */ @Deprecated @SystemApi public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, String label, Uri iconUri) throws XmlPullParserException, IOException { TvInputInfo info = new TvInputInfo.Builder(context, service) .setTvInputHardwareInfo(hardwareInfo) .setLabel(label) .build(); info.mIconUri = iconUri; return info; } /** * Create a new instance of the TvInputInfo class, instantiating it from the given Context, * ResolveInfo, and TvInputHardwareInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. * @param labelRes The label resource ID of this TvInputInfo. If it is {@code 0}, * {@code service} label will be loaded. * @param icon The {@link android.graphics.drawable.Icon} to load the icon image. If it is * {@code null}, the application icon of {@code service} will be loaded. * @hide * @deprecated Use {@link Builder} instead. */ @Deprecated @SystemApi public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service, TvInputHardwareInfo hardwareInfo, int labelRes, Icon icon) throws XmlPullParserException, IOException { return new TvInputInfo.Builder(context, service) .setTvInputHardwareInfo(hardwareInfo) .setLabel(labelRes) .setIcon(icon) .build(); } private 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) { mService = service; mId = id; mType = type; mIsHardwareInput = isHardwareInput; mLabel = label; mLabelResId = labelResId; mIcon = icon; mIconStandby = iconStandby; mIconDisconnected = iconDisconnected; mSetupActivity = setupActivity; mSettingsActivity = settingsActivity; mCanRecord = canRecord; mTunerCount = tunerCount; mHdmiDeviceInfo = hdmiDeviceInfo; mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch; mParentId = parentId; mExtras = extras; } /** * Returns a unique ID for this TV input. The ID is generated from the package and class name * implementing the TV input service. */ public String getId() { return mId; } /** * Returns the parent input ID. * *
A TV input may have a parent input if the TV input is actually a logical representation of * a device behind the hardware port represented by the parent input. * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV * input. In this case, the parent input of this logical device is the HDMI port. * *
Applications may group inputs by parent input ID to provide an easier access to inputs * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind * the same HDMI port have the same parent ID, which is the ID representing the port. Thus * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it * together using this method. * * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is * not specified. */ public String getParentId() { return mParentId; } /** * Returns the information of the service that implements this TV input. */ public ServiceInfo getServiceInfo() { return mService.serviceInfo; } /** * Returns the component of the service that implements this TV input. * @hide */ public ComponentName getComponent() { return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); } /** * Returns an intent to start the setup activity for this TV input. */ public Intent createSetupIntent() { if (!TextUtils.isEmpty(mSetupActivity)) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(mService.serviceInfo.packageName, mSetupActivity); intent.putExtra(EXTRA_INPUT_ID, getId()); return intent; } return null; } /** * Returns an intent to start the settings activity for this TV input. */ public Intent createSettingsIntent() { if (!TextUtils.isEmpty(mSettingsActivity)) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity); intent.putExtra(EXTRA_INPUT_ID, getId()); return intent; } return null; } /** * Returns the type of this TV input. */ @Type public int getType() { return mType; } /** * Returns the number of tuners this TV input has. * *
This method is valid only for inputs of type {@link #TYPE_TUNER}. For inputs of other * types, it returns 0. * *
Tuners correspond to physical/logical resources that allow reception of TV signal. Having
* N tuners means that the TV input is capable of receiving N different channels
* concurrently.
*/
public int getTunerCount() {
return mTunerCount;
}
/**
* Returns {@code true} if this TV input can record TV programs, {@code false} otherwise.
*/
public boolean canRecord() {
return mCanRecord;
}
/**
* Returns domain-specific extras associated with this TV input.
*/
public Bundle getExtras() {
return mExtras;
}
/**
* Returns the HDMI device information of this TV input.
* @hide
*/
@SystemApi
public HdmiDeviceInfo getHdmiDeviceInfo() {
if (mType == TYPE_HDMI) {
return mHdmiDeviceInfo;
}
return null;
}
/**
* Returns {@code true} if this TV input is pass-though which does not have any real channels in
* TvProvider. {@code false} otherwise.
*
* @see TvContract#buildChannelUriForPassthroughInput(String)
*/
public boolean isPassthroughInput() {
return mType != TYPE_TUNER;
}
/**
* Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner,
* HDMI1) {@code false} otherwise.
* @hide
*/
@SystemApi
public boolean isHardwareInput() {
return mIsHardwareInput;
}
/**
* Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
* the device isn't directly connected to a HDMI port.
* @hide
*/
@SystemApi
public boolean isConnectedToHdmiSwitch() {
return mIsConnectedToHdmiSwitch;
}
/**
* Checks if this TV input is marked hidden by the user in the settings.
*
* @param context Supplies a {@link Context} used to check if this TV input is hidden.
* @return {@code true} if the user marked this TV input hidden in settings. {@code false}
* otherwise.
*/
public boolean isHidden(Context context) {
return TvInputSettings.isHidden(context, mId, UserHandle.myUserId());
}
/**
* Loads the user-displayed label for this TV input.
*
* @param context Supplies a {@link Context} used to load the label.
* @return a CharSequence containing the TV input's label. If the TV input does not have
* a label, its name is returned.
*/
public CharSequence loadLabel(@NonNull Context context) {
if (mLabelResId != 0) {
return context.getPackageManager().getText(mService.serviceInfo.packageName,
mLabelResId, null);
} else if (!TextUtils.isEmpty(mLabel)) {
return mLabel;
}
return mService.loadLabel(context.getPackageManager());
}
/**
* Loads the custom label set by user in settings.
*
* @param context Supplies a {@link Context} used to load the custom label.
* @return a CharSequence containing the TV input's custom label. {@code null} if there is no
* custom label.
*/
public CharSequence loadCustomLabel(Context context) {
return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId());
}
/**
* Loads the user-displayed icon for this TV input.
*
* @param context Supplies a {@link Context} used to load the icon.
* @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
* application's icon is returned. If it's unavailable too, {@code null} is returned.
*/
public Drawable loadIcon(@NonNull Context context) {
if (mIcon != null) {
return mIcon.loadDrawable(context);
} else if (mIconUri != null) {
try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
Drawable drawable = Drawable.createFromStream(is, null);
if (drawable != null) {
return drawable;
}
} catch (IOException e) {
Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
// Falls back.
}
}
return loadServiceIcon(context);
}
/**
* Loads the user-displayed icon for this TV input per input state.
*
* @param context Supplies a {@link Context} used to load the icon.
* @param state The input state. Should be one of the followings.
* {@link TvInputManager#INPUT_STATE_CONNECTED},
* {@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} and
* {@link TvInputManager#INPUT_STATE_DISCONNECTED}.
* @return a Drawable containing the TV input's icon for the given state or {@code null} if such
* an icon is not defined.
* @hide
*/
@SystemApi
public Drawable loadIcon(@NonNull Context context, int state) {
if (state == TvInputManager.INPUT_STATE_CONNECTED) {
return loadIcon(context);
} else if (state == TvInputManager.INPUT_STATE_CONNECTED_STANDBY) {
if (mIconStandby != null) {
return mIconStandby.loadDrawable(context);
}
} else if (state == TvInputManager.INPUT_STATE_DISCONNECTED) {
if (mIconDisconnected != null) {
return mIconDisconnected.loadDrawable(context);
}
} else {
throw new IllegalArgumentException("Unknown state: " + state);
}
return null;
}
@Override
public int describeContents() {
return 0;
}
@Override
public int hashCode() {
return mId.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof TvInputInfo)) {
return false;
}
TvInputInfo obj = (TvInputInfo) o;
return Objects.equals(mService, obj.mService)
&& TextUtils.equals(mId, obj.mId)
&& mType == obj.mType
&& mIsHardwareInput == obj.mIsHardwareInput
&& TextUtils.equals(mLabel, obj.mLabel)
&& Objects.equals(mIconUri, obj.mIconUri)
&& mLabelResId == obj.mLabelResId
&& Objects.equals(mIcon, obj.mIcon)
&& Objects.equals(mIconStandby, obj.mIconStandby)
&& Objects.equals(mIconDisconnected, obj.mIconDisconnected)
&& TextUtils.equals(mSetupActivity, obj.mSetupActivity)
&& TextUtils.equals(mSettingsActivity, obj.mSettingsActivity)
&& mCanRecord == obj.mCanRecord
&& mTunerCount == obj.mTunerCount
&& Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
&& mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
&& TextUtils.equals(mParentId, obj.mParentId)
&& Objects.equals(mExtras, obj.mExtras);
}
@Override
public String toString() {
return "TvInputInfo{id=" + mId
+ ", pkg=" + mService.serviceInfo.packageName
+ ", service=" + mService.serviceInfo.name + "}";
}
/**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mService.writeToParcel(dest, flags);
dest.writeString(mId);
dest.writeInt(mType);
dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
TextUtils.writeToParcel(mLabel, dest, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeInt(mLabelResId);
dest.writeParcelable(mIcon, flags);
dest.writeParcelable(mIconStandby, flags);
dest.writeParcelable(mIconDisconnected, flags);
dest.writeString(mSetupActivity);
dest.writeString(mSettingsActivity);
dest.writeByte(mCanRecord ? (byte) 1 : 0);
dest.writeInt(mTunerCount);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
dest.writeString(mParentId);
dest.writeBundle(mExtras);
}
private Drawable loadServiceIcon(Context context) {
if (mService.serviceInfo.icon == 0
&& mService.serviceInfo.applicationInfo.icon == 0) {
return null;
}
return mService.serviceInfo.loadIcon(context.getPackageManager());
}
public static final Parcelable.Creator