• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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