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 INTERACTIVE_APP_TYPE_TARGETED_AD, 61 INTERACTIVE_APP_TYPE_OTHER 62 }) 63 public @interface InteractiveAppType {} 64 65 /** HbbTV interactive app type */ 66 public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1; 67 /** ATSC interactive app type */ 68 public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2; 69 /** Ginga interactive app type */ 70 public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4; 71 /** Targeted Advertisement interactive app type */ 72 public static final int INTERACTIVE_APP_TYPE_TARGETED_AD = 0x8; 73 /** Other interactive app type */ 74 public static final int INTERACTIVE_APP_TYPE_OTHER = 0x80000000; 75 76 private final ResolveInfo mService; 77 private final String mId; 78 private int mTypes; 79 private final List<String> mExtraTypes = new ArrayList<>(); 80 81 /** 82 * Constructs a TvInteractiveAppServiceInfo object. 83 * 84 * @param context the application context 85 * @param component the component name of the TvInteractiveAppService 86 */ TvInteractiveAppServiceInfo(@onNull Context context, @NonNull ComponentName component)87 public TvInteractiveAppServiceInfo(@NonNull Context context, @NonNull ComponentName component) { 88 if (context == null) { 89 throw new IllegalArgumentException("context cannot be null."); 90 } 91 Intent intent = 92 new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component); 93 ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, 94 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 95 if (resolveInfo == null) { 96 throw new IllegalArgumentException("Invalid component. Can't find the service."); 97 } 98 99 ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName, 100 resolveInfo.serviceInfo.name); 101 String id; 102 id = generateInteractiveAppServiceId(componentName); 103 List<String> types = new ArrayList<>(); 104 parseServiceMetadata(resolveInfo, context, types); 105 106 mService = resolveInfo; 107 mId = id; 108 toTypesFlag(types); 109 } TvInteractiveAppServiceInfo( ResolveInfo service, String id, int types, List<String> extraTypes)110 private TvInteractiveAppServiceInfo( 111 ResolveInfo service, String id, int types, List<String> extraTypes) { 112 mService = service; 113 mId = id; 114 mTypes = types; 115 mExtraTypes.addAll(extraTypes); 116 } 117 TvInteractiveAppServiceInfo(@onNull Parcel in)118 private TvInteractiveAppServiceInfo(@NonNull Parcel in) { 119 mService = ResolveInfo.CREATOR.createFromParcel(in); 120 mId = in.readString(); 121 mTypes = in.readInt(); 122 in.readStringList(mExtraTypes); 123 } 124 125 public static final @NonNull Creator<TvInteractiveAppServiceInfo> CREATOR = 126 new Creator<TvInteractiveAppServiceInfo>() { 127 @Override 128 public TvInteractiveAppServiceInfo createFromParcel(Parcel in) { 129 return new TvInteractiveAppServiceInfo(in); 130 } 131 132 @Override 133 public TvInteractiveAppServiceInfo[] newArray(int size) { 134 return new TvInteractiveAppServiceInfo[size]; 135 } 136 }; 137 138 @Override describeContents()139 public int describeContents() { 140 return 0; 141 } 142 143 @Override writeToParcel(@onNull Parcel dest, int flags)144 public void writeToParcel(@NonNull Parcel dest, int flags) { 145 mService.writeToParcel(dest, flags); 146 dest.writeString(mId); 147 dest.writeInt(mTypes); 148 dest.writeStringList(mExtraTypes); 149 } 150 151 /** 152 * Returns a unique ID for this TV interactive app service. The ID is generated from the package 153 * and class name implementing the TV interactive app service. 154 */ 155 @NonNull getId()156 public String getId() { 157 return mId; 158 } 159 160 /** 161 * Returns the component of the TV Interactive App service. 162 * @hide 163 */ getComponent()164 public ComponentName getComponent() { 165 return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); 166 } 167 168 /** 169 * Returns the information of the service that implements this TV Interactive App service. 170 */ 171 @Nullable getServiceInfo()172 public ServiceInfo getServiceInfo() { 173 return mService.serviceInfo; 174 } 175 176 /** 177 * Gets supported interactive app types. 178 * 179 * <p>The supported interactive app types is in a bit map format. For example: 180 * <pre><code> 181 * int types = tvInteractiveAppInfo.getSupportedTypes(); 182 * if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_HBBTV != 0) { 183 * // HbbTV type is supported. Do something... 184 * } 185 * if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_ATSC == 0) { 186 * // ATSC type is not supported. Do something... 187 * } 188 * </code></pre> 189 * 190 * @return An int bit map representing supported types. 191 */ 192 @InteractiveAppType 193 @NonNull getSupportedTypes()194 public int getSupportedTypes() { 195 return mTypes; 196 } 197 198 /** 199 * Gets custom supported interactive app types which are not listed. 200 * 201 * @see #getSupportedTypes() 202 */ 203 @NonNull getCustomSupportedTypes()204 public List<String> getCustomSupportedTypes() { 205 return mExtraTypes; 206 } 207 generateInteractiveAppServiceId(ComponentName name)208 private static String generateInteractiveAppServiceId(ComponentName name) { 209 return name.flattenToShortString(); 210 } 211 parseServiceMetadata( ResolveInfo resolveInfo, Context context, List<String> types)212 private static void parseServiceMetadata( 213 ResolveInfo resolveInfo, Context context, List<String> types) { 214 ServiceInfo si = resolveInfo.serviceInfo; 215 PackageManager pm = context.getPackageManager(); 216 try (XmlResourceParser parser = 217 si.loadXmlMetaData(pm, TvInteractiveAppService.SERVICE_META_DATA)) { 218 if (parser == null) { 219 throw new IllegalStateException( 220 "No " + TvInteractiveAppService.SERVICE_META_DATA 221 + " meta-data found for " + si.name); 222 } 223 224 Resources res = pm.getResourcesForApplication(si.applicationInfo); 225 AttributeSet attrs = Xml.asAttributeSet(parser); 226 227 int type; 228 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 229 && type != XmlPullParser.START_TAG) { 230 // move to the START_TAG 231 } 232 233 String nodeName = parser.getName(); 234 if (!XML_START_TAG_NAME.equals(nodeName)) { 235 throw new IllegalStateException("Meta-data does not start with " 236 + XML_START_TAG_NAME + " tag for " + si.name); 237 } 238 239 TypedArray sa = res.obtainAttributes(attrs, 240 com.android.internal.R.styleable.TvInteractiveAppService); 241 CharSequence[] textArr = sa.getTextArray( 242 com.android.internal.R.styleable.TvInteractiveAppService_supportedTypes); 243 for (CharSequence cs : textArr) { 244 types.add(cs.toString().toLowerCase()); 245 } 246 247 sa.recycle(); 248 } catch (IOException | XmlPullParserException e) { 249 throw new IllegalStateException( 250 "Failed reading meta-data for " + si.packageName, e); 251 } catch (PackageManager.NameNotFoundException e) { 252 throw new IllegalStateException("No resources found for " + si.packageName, e); 253 } 254 } 255 toTypesFlag(List<String> types)256 private void toTypesFlag(List<String> types) { 257 mTypes = 0; 258 mExtraTypes.clear(); 259 for (String type : types) { 260 switch (type) { 261 case "hbbtv": 262 mTypes |= INTERACTIVE_APP_TYPE_HBBTV; 263 break; 264 case "atsc": 265 mTypes |= INTERACTIVE_APP_TYPE_ATSC; 266 break; 267 case "ginga": 268 mTypes |= INTERACTIVE_APP_TYPE_GINGA; 269 break; 270 case "targeted_ad": 271 mTypes |= INTERACTIVE_APP_TYPE_TARGETED_AD; 272 default: 273 mTypes |= INTERACTIVE_APP_TYPE_OTHER; 274 mExtraTypes.add(type); 275 break; 276 } 277 } 278 } 279 } 280