2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.media.tv;
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.hardware.hdmi.HdmiDeviceInfo;
33 import android.net.Uri;
34 import android.os.Parcel;
35 import android.os.Parcelable;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.util.AttributeSet;
40 import android.util.Log;
41 import android.util.SparseIntArray;
42 import android.util.Xml;
44 import org.xmlpull.v1.XmlPullParser;
45 import org.xmlpull.v1.XmlPullParserException;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.util.HashMap;
50 import java.util.HashSet;
55 * This class is used to specify meta information of a TV input.
57 public final class TvInputInfo implements Parcelable {
58 private static final boolean DEBUG = false;
59 private static final String TAG = "TvInputInfo";
61 // Should be in sync with frameworks/base/core/res/res/values/attrs.xml
63 * TV input type: the TV input service is a tuner which provides channels.
65 public static final int TYPE_TUNER = 0;
67 * TV input type: a generic hardware TV input type.
69 public static final int TYPE_OTHER = 1000;
71 * TV input type: the TV input service represents a composite port.
73 public static final int TYPE_COMPOSITE = 1001;
75 * TV input type: the TV input service represents a SVIDEO port.
77 public static final int TYPE_SVIDEO = 1002;
79 * TV input type: the TV input service represents a SCART port.
81 public static final int TYPE_SCART = 1003;
83 * TV input type: the TV input service represents a component port.
85 public static final int TYPE_COMPONENT = 1004;
87 * TV input type: the TV input service represents a VGA port.
89 public static final int TYPE_VGA = 1005;
91 * TV input type: the TV input service represents a DVI port.
93 public static final int TYPE_DVI = 1006;
95 * TV input type: the TV input service is HDMI. (e.g. HDMI 1)
97 public static final int TYPE_HDMI = 1007;
99 * TV input type: the TV input service represents a display port.
101 public static final int TYPE_DISPLAY_PORT = 1008;
104 * The ID of the TV input to provide to the setup activity and settings activity.
106 public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID";
108 private static SparseIntArray sHardwareTypeToTvInputType = new SparseIntArray();
110 private static final String XML_START_TAG_NAME = "tv-input";
111 private static final String DELIMITER_INFO_IN_ID = "/";
112 private static final String PREFIX_HDMI_DEVICE = "HDMI";
113 private static final String PREFIX_HARDWARE_DEVICE = "HW";
114 private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4;
115 private static final int LENGTH_HDMI_DEVICE_ID = 2;
117 private final ResolveInfo mService;
118 private final String mId;
119 private final String mParentId;
120 private final int mType;
121 private final boolean mIsHardwareInput;
123 // Attributes from XML meta data.
124 private String mSetupActivity;
125 private String mSettingsActivity;
127 private HdmiDeviceInfo mHdmiDeviceInfo;
128 private String mLabel;
129 private Uri mIconUri;
130 private boolean mIsConnectedToHdmiSwitch;
133 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE,
135 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_TUNER, TYPE_TUNER);
136 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPOSITE, TYPE_COMPOSITE);
137 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SVIDEO, TYPE_SVIDEO);
138 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_SCART, TYPE_SCART);
139 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_COMPONENT, TYPE_COMPONENT);
140 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_VGA, TYPE_VGA);
141 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DVI, TYPE_DVI);
142 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI, TYPE_HDMI);
143 sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_DISPLAY_PORT,
148 * Create a new instance of the TvInputInfo class,
149 * instantiating it from the given Context and ResolveInfo.
151 * @param service The ResolveInfo returned from the package manager about this TV input service.
154 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
155 throws XmlPullParserException, IOException {
156 return createTvInputInfo(context, service, generateInputIdForComponentName(
157 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)),
158 null, TYPE_TUNER, false, null, null, false);
162 * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
163 * ResolveInfo, and HdmiDeviceInfo.
165 * @param service The ResolveInfo returned from the package manager about this TV input service.
166 * @param hdmiDeviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
167 * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
168 * @param iconUri The {@link android.net.Uri} to load the icon image. See
169 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
170 * the application icon of {@code service} will be loaded.
171 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
172 * label will be loaded.
176 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
177 HdmiDeviceInfo hdmiDeviceInfo, String parentId, String label, Uri iconUri)
178 throws XmlPullParserException, IOException {
179 boolean isConnectedToHdmiSwitch = (hdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
180 TvInputInfo input = createTvInputInfo(context, service, generateInputIdForHdmiDevice(
181 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
182 hdmiDeviceInfo), parentId, TYPE_HDMI, true, label, iconUri, isConnectedToHdmiSwitch);
183 input.mHdmiDeviceInfo = hdmiDeviceInfo;
188 * Create a new instance of the TvInputInfo class, instantiating it from the given Context,
189 * ResolveInfo, and TvInputHardwareInfo.
191 * @param service The ResolveInfo returned from the package manager about this TV input service.
192 * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
193 * @param iconUri The {@link android.net.Uri} to load the icon image. See
194 * {@link android.content.ContentResolver#openInputStream}. If it is {@code null},
195 * the application icon of {@code service} will be loaded.
196 * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service}
197 * label will be loaded.
201 public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
202 TvInputHardwareInfo hardwareInfo, String label, Uri iconUri)
203 throws XmlPullParserException, IOException {
204 int inputType = sHardwareTypeToTvInputType.get(hardwareInfo.getType(), TYPE_TUNER);
205 return createTvInputInfo(context, service, generateInputIdForHardware(
206 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
207 hardwareInfo), null, inputType, true, label, iconUri, false);
210 private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
211 String id, String parentId, int inputType, boolean isHardwareInput, String label,
212 Uri iconUri, boolean isConnectedToHdmiSwitch)
213 throws XmlPullParserException, IOException {
214 ServiceInfo si = service.serviceInfo;
215 PackageManager pm = context.getPackageManager();
216 XmlResourceParser parser = null;
218 parser = si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA);
219 if (parser == null) {
220 throw new XmlPullParserException("No " + TvInputService.SERVICE_META_DATA
221 + " meta-data for " + si.name);
224 Resources res = pm.getResourcesForApplication(si.applicationInfo);
225 AttributeSet attrs = Xml.asAttributeSet(parser);
228 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
229 && type != XmlPullParser.START_TAG) {
232 String nodeName = parser.getName();
233 if (!XML_START_TAG_NAME.equals(nodeName)) {
234 throw new XmlPullParserException(
235 "Meta-data does not start with tv-input-service tag in " + si.name);
238 TvInputInfo input = new TvInputInfo(service, id, parentId, inputType, isHardwareInput);
239 TypedArray sa = res.obtainAttributes(attrs,
240 com.android.internal.R.styleable.TvInputService);
241 input.mSetupActivity = sa.getString(
242 com.android.internal.R.styleable.TvInputService_setupActivity);
244 Log.d(TAG, "Setup activity loaded. [" + input.mSetupActivity + "] for " + si.name);
246 if (inputType == TYPE_TUNER && TextUtils.isEmpty(input.mSetupActivity)) {
247 throw new XmlPullParserException("Setup activity not found in " + si.name);
249 input.mSettingsActivity = sa.getString(
250 com.android.internal.R.styleable.TvInputService_settingsActivity);
252 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
257 input.mLabel = label;
258 input.mIconUri = iconUri;
259 input.mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
261 } catch (NameNotFoundException e) {
262 throw new XmlPullParserException("Unable to create context for: " + si.packageName);
264 if (parser != null) {
273 * @param service The ResolveInfo returned from the package manager about this TV input service.
274 * @param id ID of this TV input. Should be generated via generateInputId*().
275 * @param parentId ID of this TV input's parent input. {@code null} if none exists.
276 * @param type The type of this TV input service.
277 * @param isHardwareInput {@code true} if this TV input represents a hardware device.
278 * {@code false} otherwise.
280 private TvInputInfo(ResolveInfo service, String id, String parentId, int type,
281 boolean isHardwareInput) {
284 mParentId = parentId;
286 mIsHardwareInput = isHardwareInput;
290 * Returns a unique ID for this TV input. The ID is generated from the package and class name
291 * implementing the TV input service.
293 public String getId() {
298 * Returns the parent input ID.
300 * <p>A TV input may have a parent input if the TV input is actually a logical representation of
301 * a device behind the hardware port represented by the parent input.
302 * For example, a HDMI CEC logical device, connected to a HDMI port, appears as another TV
303 * input. In this case, the parent input of this logical device is the HDMI port.
305 * <p>Applications may group inputs by parent input ID to provide an easier access to inputs
306 * sharing the same physical port. In the example of HDMI CEC, logical HDMI CEC devices behind
307 * the same HDMI port have the same parent ID, which is the ID representing the port. Thus
308 * applications can group the hardware HDMI port and the logical HDMI CEC devices behind it
309 * together using this method.
311 * @return the ID of the parent input, if exists. Returns {@code null} if the parent input is
314 public String getParentId() {
319 * Returns the information of the service that implements this TV input.
321 public ServiceInfo getServiceInfo() {
322 return mService.serviceInfo;
326 * Returns the component of the service that implements this TV input.
329 public ComponentName getComponent() {
330 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
334 * Returns an intent to start the setup activity for this TV input.
336 public Intent createSetupIntent() {
337 if (!TextUtils.isEmpty(mSetupActivity)) {
338 Intent intent = new Intent(Intent.ACTION_MAIN);
339 intent.setClassName(mService.serviceInfo.packageName, mSetupActivity);
340 intent.putExtra(EXTRA_INPUT_ID, getId());
347 * Returns an intent to start the settings activity for this TV input.
349 public Intent createSettingsIntent() {
350 if (!TextUtils.isEmpty(mSettingsActivity)) {
351 Intent intent = new Intent(Intent.ACTION_MAIN);
352 intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity);
353 intent.putExtra(EXTRA_INPUT_ID, getId());
360 * Returns the type of this TV input.
362 public int getType() {
367 * Returns the HDMI device information of this TV input.
371 public HdmiDeviceInfo getHdmiDeviceInfo() {
372 if (mType == TYPE_HDMI) {
373 return mHdmiDeviceInfo;
379 * Returns {@code true} if this TV input is pass-though which does not have any real channels in
380 * TvProvider. {@code false} otherwise.
382 * @see TvContract#buildChannelUriForPassthroughInput(String)
384 public boolean isPassthroughInput() {
385 return mType != TYPE_TUNER;
389 * Returns {@code true} if this TV input represents a hardware device. (e.g. built-in tuner,
390 * HDMI1) {@code false} otherwise.
394 public boolean isHardwareInput() {
395 return mIsHardwareInput;
399 * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
400 * the device isn't directly connected to a HDMI port.
404 public boolean isConnectedToHdmiSwitch() {
405 return mIsConnectedToHdmiSwitch;
409 * Checks if this TV input is marked hidden by the user in the settings.
411 * @param context Supplies a {@link Context} used to check if this TV input is hidden.
412 * @return {@code true} if the user marked this TV input hidden in settings. {@code false}
417 public boolean isHidden(Context context) {
418 return TvInputSettings.isHidden(context, mId, UserHandle.myUserId());
422 * Loads the user-displayed label for this TV input.
424 * @param context Supplies a {@link Context} used to load the label.
425 * @return a CharSequence containing the TV input's label. If the TV input does not have
426 * a label, its name is returned.
428 public CharSequence loadLabel(@NonNull Context context) {
429 if (TextUtils.isEmpty(mLabel)) {
430 return mService.loadLabel(context.getPackageManager());
437 * Loads the custom label set by user in settings.
439 * @param context Supplies a {@link Context} used to load the custom label.
440 * @return a CharSequence containing the TV input's custom label. {@code null} if there is no
445 public CharSequence loadCustomLabel(Context context) {
446 return TvInputSettings.getCustomLabel(context, mId, UserHandle.myUserId());
450 * Loads the user-displayed icon for this TV input.
452 * @param context Supplies a {@link Context} used to load the icon.
453 * @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
454 * application's icon is returned. If it's unavailable too, {@code null} is returned.
456 public Drawable loadIcon(@NonNull Context context) {
457 if (mIconUri == null) {
458 return loadServiceIcon(context);
460 try (InputStream is = context.getContentResolver().openInputStream(mIconUri)) {
461 Drawable drawable = Drawable.createFromStream(is, null);
462 if (drawable == null) {
463 return loadServiceIcon(context);
466 } catch (IOException e) {
467 Log.w(TAG, "Loading the default icon due to a failure on loading " + mIconUri, e);
468 return loadServiceIcon(context);
473 public int describeContents() {
478 public int hashCode() {
479 return mId.hashCode();
483 public boolean equals(Object o) {
488 if (!(o instanceof TvInputInfo)) {
492 TvInputInfo obj = (TvInputInfo) o;
493 return mId.equals(obj.mId);
497 public String toString() {
498 return "TvInputInfo{id=" + mId
499 + ", pkg=" + mService.serviceInfo.packageName
500 + ", service=" + mService.serviceInfo.name + "}";
504 * Used to package this object into a {@link Parcel}.
506 * @param dest The {@link Parcel} to be written.
507 * @param flags The flags used for parceling.
510 public void writeToParcel(@NonNull Parcel dest, int flags) {
511 dest.writeString(mId);
512 dest.writeString(mParentId);
513 mService.writeToParcel(dest, flags);
514 dest.writeString(mSetupActivity);
515 dest.writeString(mSettingsActivity);
516 dest.writeInt(mType);
517 dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
518 dest.writeParcelable(mHdmiDeviceInfo, flags);
519 dest.writeParcelable(mIconUri, flags);
520 dest.writeString(mLabel);
521 dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
524 private Drawable loadServiceIcon(Context context) {
525 if (mService.serviceInfo.icon == 0
526 && mService.serviceInfo.applicationInfo.icon == 0) {
529 return mService.serviceInfo.loadIcon(context.getPackageManager());
533 * Used to generate an input id from a ComponentName.
535 * @param name the component name for generating an input id.
536 * @return the generated input id for the given {@code name}.
538 private static final String generateInputIdForComponentName(ComponentName name) {
539 return name.flattenToShortString();
543 * Used to generate an input id from a ComponentName and HdmiDeviceInfo.
545 * @param name the component name for generating an input id.
546 * @param deviceInfo HdmiDeviceInfo describing this TV input.
547 * @return the generated input id for the given {@code name} and {@code deviceInfo}.
549 private static final String generateInputIdForHdmiDevice(
550 ComponentName name, HdmiDeviceInfo deviceInfo) {
551 // Example of the format : "/HDMI%04X%02X"
552 String format = String.format("%s%s%%0%sX%%0%sX", DELIMITER_INFO_IN_ID, PREFIX_HDMI_DEVICE,
553 LENGTH_HDMI_PHYSICAL_ADDRESS, LENGTH_HDMI_DEVICE_ID);
554 return name.flattenToShortString() + String.format(format,
555 deviceInfo.getPhysicalAddress(), deviceInfo.getId());
559 * Used to generate an input id from a ComponentName and TvInputHardwareInfo
561 * @param name the component name for generating an input id.
562 * @param hardwareInfo TvInputHardwareInfo describing this TV input.
563 * @return the generated input id for the given {@code name} and {@code hardwareInfo}.
565 private static final String generateInputIdForHardware(
566 ComponentName name, TvInputHardwareInfo hardwareInfo) {
567 return name.flattenToShortString() + String.format("%s%s%d",
568 DELIMITER_INFO_IN_ID, PREFIX_HARDWARE_DEVICE, hardwareInfo.getDeviceId());
571 public static final Parcelable.Creator<TvInputInfo> CREATOR =
572 new Parcelable.Creator<TvInputInfo>() {
574 public TvInputInfo createFromParcel(Parcel in) {
575 return new TvInputInfo(in);
579 public TvInputInfo[] newArray(int size) {
580 return new TvInputInfo[size];
584 private TvInputInfo(Parcel in) {
585 mId = in.readString();
586 mParentId = in.readString();
587 mService = ResolveInfo.CREATOR.createFromParcel(in);
588 mSetupActivity = in.readString();
589 mSettingsActivity = in.readString();
590 mType = in.readInt();
591 mIsHardwareInput = in.readByte() == 1 ? true : false;
592 mHdmiDeviceInfo = in.readParcelable(null);
593 mIconUri = in.readParcelable(null);
594 mLabel = in.readString();
595 mIsConnectedToHdmiSwitch = in.readByte() == 1 ? true : false;
599 * Utility class for putting and getting settings for TV input.
604 public static final class TvInputSettings {
605 private static final String TV_INPUT_SEPARATOR = ":";
606 private static final String CUSTOM_NAME_SEPARATOR = ",";
608 private TvInputSettings() { }
610 private static boolean isHidden(Context context, String inputId, int userId) {
611 return getHiddenTvInputIds(context, userId).contains(inputId);
614 private static String getCustomLabel(Context context, String inputId, int userId) {
615 return getCustomLabels(context, userId).get(inputId);
619 * Returns a set of TV input IDs which are marked as hidden by user in the settings.
621 * @param context The application context
622 * @param userId The user ID for the stored hidden input set
626 public static Set<String> getHiddenTvInputIds(Context context, int userId) {
627 String hiddenIdsString = Settings.Secure.getStringForUser(
628 context.getContentResolver(), Settings.Secure.TV_INPUT_HIDDEN_INPUTS, userId);
629 Set<String> set = new HashSet<String>();
630 if (TextUtils.isEmpty(hiddenIdsString)) {
633 String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR);
634 for (String id : ids) {
635 set.add(Uri.decode(id));
641 * Returns a map of TV input ID/custom label pairs set by the user in the settings.
643 * @param context The application context
644 * @param userId The user ID for the stored hidden input map
648 public static Map<String, String> getCustomLabels(Context context, int userId) {
649 String labelsString = Settings.Secure.getStringForUser(
650 context.getContentResolver(), Settings.Secure.TV_INPUT_CUSTOM_LABELS, userId);
651 Map<String, String> map = new HashMap<String, String>();
652 if (TextUtils.isEmpty(labelsString)) {
655 String[] pairs = labelsString.split(TV_INPUT_SEPARATOR);
656 for (String pairString : pairs) {
657 String[] pair = pairString.split(CUSTOM_NAME_SEPARATOR);
658 map.put(Uri.decode(pair[0]), Uri.decode(pair[1]));
664 * Stores a set of TV input IDs which are marked as hidden by user. This is expected to
665 * be called from the settings app.
667 * @param context The application context
668 * @param hiddenInputIds A set including all the hidden TV input IDs
669 * @param userId The user ID for the stored hidden input set
673 public static void putHiddenTvInputs(Context context, Set<String> hiddenInputIds,
675 StringBuilder builder = new StringBuilder();
676 boolean firstItem = true;
677 for (String inputId : hiddenInputIds) {
678 ensureValidField(inputId);
682 builder.append(TV_INPUT_SEPARATOR);
684 builder.append(Uri.encode(inputId));
686 Settings.Secure.putStringForUser(context.getContentResolver(),
687 Settings.Secure.TV_INPUT_HIDDEN_INPUTS, builder.toString(), userId);
691 * Stores a map of TV input ID/custom label set by user. This is expected to be
692 * called from the settings app.
694 * @param context The application context.
695 * @param customLabels A map of TV input ID/custom label pairs
696 * @param userId The user ID for the stored hidden input map
700 public static void putCustomLabels(Context context,
701 Map<String, String> customLabels, int userId) {
702 StringBuilder builder = new StringBuilder();
703 boolean firstItem = true;
704 for (Map.Entry<String, String> entry: customLabels.entrySet()) {
705 ensureValidField(entry.getKey());
706 ensureValidField(entry.getValue());
710 builder.append(TV_INPUT_SEPARATOR);
712 builder.append(Uri.encode(entry.getKey()));
713 builder.append(CUSTOM_NAME_SEPARATOR);
714 builder.append(Uri.encode(entry.getValue()));
716 Settings.Secure.putStringForUser(context.getContentResolver(),
717 Settings.Secure.TV_INPUT_CUSTOM_LABELS, builder.toString(), userId);
720 private static void ensureValidField(String value) {
721 if (TextUtils.isEmpty(value)) {
722 throw new IllegalArgumentException(value + " should not empty ");