1 /* 2 * Copyright 2021 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.interactive; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 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.os.Parcel; 32 import android.os.Parcelable; 33 import android.util.AttributeSet; 34 import android.util.Xml; 35 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.IOException; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** 46 * This class is used to specify meta information of a TV interactive app. 47 */ 48 public final class TvInteractiveAppServiceInfo implements Parcelable { 49 private static final boolean DEBUG = false; 50 private static final String TAG = "TvInteractiveAppServiceInfo"; 51 52 private static final String XML_START_TAG_NAME = "tv-interactive-app"; 53 54 /** @hide */ 55 @Retention(RetentionPolicy.SOURCE) 56 @IntDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = { 57 INTERACTIVE_APP_TYPE_HBBTV, 58 INTERACTIVE_APP_TYPE_ATSC, 59 INTERACTIVE_APP_TYPE_GINGA, 60 }) 61 public @interface InteractiveAppType {} 62 63 /** HbbTV interactive app type */ 64 public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1; 65 /** ATSC interactive app type */ 66 public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2; 67 /** Ginga interactive app type */ 68 public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4; 69 70 private final ResolveInfo mService; 71 private final String mId; 72 private int mTypes; 73 74 /** 75 * Constructs a TvInteractiveAppServiceInfo object. 76 * 77 * @param context the application context 78 * @param component the component name of the TvInteractiveAppService 79 */ TvInteractiveAppServiceInfo(@onNull Context context, @NonNull ComponentName component)80 public TvInteractiveAppServiceInfo(@NonNull Context context, @NonNull ComponentName component) { 81 if (context == null) { 82 throw new IllegalArgumentException("context cannot be null."); 83 } 84 Intent intent = 85 new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component); 86 ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, 87 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 88 if (resolveInfo == null) { 89 throw new IllegalArgumentException("Invalid component. Can't find the service."); 90 } 91 92 ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName, 93 resolveInfo.serviceInfo.name); 94 String id; 95 id = generateInteractiveAppServiceId(componentName); 96 List<String> types = new ArrayList<>(); 97 parseServiceMetadata(resolveInfo, context, types); 98 99 mService = resolveInfo; 100 mId = id; 101 mTypes = toTypesFlag(types); 102 } TvInteractiveAppServiceInfo(ResolveInfo service, String id, int types)103 private TvInteractiveAppServiceInfo(ResolveInfo service, String id, int types) { 104 mService = service; 105 mId = id; 106 mTypes = types; 107 } 108 TvInteractiveAppServiceInfo(@onNull Parcel in)109 private TvInteractiveAppServiceInfo(@NonNull Parcel in) { 110 mService = ResolveInfo.CREATOR.createFromParcel(in); 111 mId = in.readString(); 112 mTypes = in.readInt(); 113 } 114 115 public static final @NonNull Creator<TvInteractiveAppServiceInfo> CREATOR = 116 new Creator<TvInteractiveAppServiceInfo>() { 117 @Override 118 public TvInteractiveAppServiceInfo createFromParcel(Parcel in) { 119 return new TvInteractiveAppServiceInfo(in); 120 } 121 122 @Override 123 public TvInteractiveAppServiceInfo[] newArray(int size) { 124 return new TvInteractiveAppServiceInfo[size]; 125 } 126 }; 127 128 @Override describeContents()129 public int describeContents() { 130 return 0; 131 } 132 133 @Override writeToParcel(@onNull Parcel dest, int flags)134 public void writeToParcel(@NonNull Parcel dest, int flags) { 135 mService.writeToParcel(dest, flags); 136 dest.writeString(mId); 137 dest.writeInt(mTypes); 138 } 139 140 /** 141 * Returns a unique ID for this TV interactive app service. The ID is generated from the package 142 * and class name implementing the TV interactive app service. 143 */ 144 @NonNull getId()145 public String getId() { 146 return mId; 147 } 148 149 /** 150 * Returns the component of the TV Interactive App service. 151 * @hide 152 */ getComponent()153 public ComponentName getComponent() { 154 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); 155 } 156 157 /** 158 * Returns the information of the service that implements this TV Interactive App service. 159 */ 160 @Nullable getServiceInfo()161 public ServiceInfo getServiceInfo() { 162 return mService.serviceInfo; 163 } 164 165 /** 166 * Gets supported interactive app types 167 */ 168 @InteractiveAppType 169 @NonNull getSupportedTypes()170 public int getSupportedTypes() { 171 return mTypes; 172 } 173 generateInteractiveAppServiceId(ComponentName name)174 private static String generateInteractiveAppServiceId(ComponentName name) { 175 return name.flattenToShortString(); 176 } 177 parseServiceMetadata( ResolveInfo resolveInfo, Context context, List<String> types)178 private static void parseServiceMetadata( 179 ResolveInfo resolveInfo, Context context, List<String> types) { 180 ServiceInfo si = resolveInfo.serviceInfo; 181 PackageManager pm = context.getPackageManager(); 182 try (XmlResourceParser parser = 183 si.loadXmlMetaData(pm, TvInteractiveAppService.SERVICE_META_DATA)) { 184 if (parser == null) { 185 throw new IllegalStateException( 186 "No " + TvInteractiveAppService.SERVICE_META_DATA 187 + " meta-data found for " + si.name); 188 } 189 190 Resources res = pm.getResourcesForApplication(si.applicationInfo); 191 AttributeSet attrs = Xml.asAttributeSet(parser); 192 193 int type; 194 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 195 && type != XmlPullParser.START_TAG) { 196 // move to the START_TAG 197 } 198 199 String nodeName = parser.getName(); 200 if (!XML_START_TAG_NAME.equals(nodeName)) { 201 throw new IllegalStateException("Meta-data does not start with " 202 + XML_START_TAG_NAME + " tag for " + si.name); 203 } 204 205 TypedArray sa = res.obtainAttributes(attrs, 206 com.android.internal.R.styleable.TvInteractiveAppService); 207 CharSequence[] textArr = sa.getTextArray( 208 com.android.internal.R.styleable.TvInteractiveAppService_supportedTypes); 209 for (CharSequence cs : textArr) { 210 types.add(cs.toString().toLowerCase()); 211 } 212 213 sa.recycle(); 214 } catch (IOException | XmlPullParserException e) { 215 throw new IllegalStateException( 216 "Failed reading meta-data for " + si.packageName, e); 217 } catch (PackageManager.NameNotFoundException e) { 218 throw new IllegalStateException("No resources found for " + si.packageName, e); 219 } 220 } 221 toTypesFlag(List<String> types)222 private static int toTypesFlag(List<String> types) { 223 int flag = 0; 224 for (String type : types) { 225 switch (type) { 226 case "hbbtv": 227 flag |= INTERACTIVE_APP_TYPE_HBBTV; 228 break; 229 case "atsc": 230 flag |= INTERACTIVE_APP_TYPE_ATSC; 231 break; 232 case "ginga": 233 flag |= INTERACTIVE_APP_TYPE_GINGA; 234 break; 235 default: 236 break; 237 } 238 } 239 return flag; 240 } 241 } 242