1/*
2 * Copyright (C) 2015 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 */
16package com.android.car.pm;
17
18import android.content.Context;
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageManager;
21import android.content.pm.PackageManager.NameNotFoundException;
22import android.content.res.Resources;
23import android.content.res.XmlResourceParser;
24import android.util.Log;
25
26import com.android.car.CarLog;
27
28import java.io.IOException;
29import java.util.LinkedList;
30
31import org.xmlpull.v1.XmlPullParser;
32import org.xmlpull.v1.XmlPullParserException;
33
34/**
35 * Read meta data containing car application info.
36 */
37public class CarAppMetadataReader {
38    /** Name of the meta-data attribute for the automotive application XML resource */
39    private static final String METADATA_ATTRIBUTE = "android.car.application";
40    /** Name of the tag to declare automotive usage */
41    private static final String USES_TAG = "uses";
42    /** Name of the attribute to name the usage type */
43    private static final String NAME_ATTRIBUTE = "name";
44
45    private static final String NAME_ATTR_TYPE_SERVICE = "service";
46    private static final String NAME_ATTR_TYPE_ACTIVITY = "activity";
47
48    private static final String CLASS_ATTRIBUTE = "class";
49
50    public static CarAppMetadataInfo parseMetadata(Context context, String packageName) {
51        int metadataId = 0;
52        Resources resources = null;
53        try {
54            metadataId = getMetadataId(context, packageName);
55            if (metadataId == 0) {
56                return null;
57            }
58            PackageManager pm = context.getPackageManager();
59            resources = pm.getResourcesForApplication(packageName);
60        } catch (NameNotFoundException e) {
61            Log.w(CarLog.TAG_PACKAGE, "Cannot read mta data, package:" + packageName, e);
62            return null;
63        }
64
65        // Try to open the XML resource
66        XmlResourceParser parser = null;
67        try {
68            parser = resources.getXml(metadataId);
69        } catch (Resources.NotFoundException e) {
70            Log.w(CarLog.TAG_PACKAGE, "Resource not found [" + packageName + "]");
71            return null;
72        }
73
74        boolean useService = false;
75        boolean useAllActivities = false;
76        LinkedList<String> activities = null;
77        // Now, read the XML and pull out the appropriate "uses" attribute
78        int eventType;
79        try {
80            eventType = parser.next();
81            while (eventType != XmlPullParser.END_DOCUMENT) {
82                if (eventType == XmlPullParser.START_TAG && USES_TAG.equals(parser.getName())) {
83                    String nameAttribute =
84                            parser.getAttributeValue(null /*namespace*/, NAME_ATTRIBUTE);
85                    if (nameAttribute == null) {
86                        Log.w(CarLog.TAG_PACKAGE, "Bad metadata," + METADATA_ATTRIBUTE +
87                                " for pavkage:" + packageName);
88                        return null;
89                    }
90                    switch (nameAttribute) {
91                        case NAME_ATTR_TYPE_SERVICE:
92                            useService = true;
93                            break;
94                        case NAME_ATTR_TYPE_ACTIVITY: {
95                            String classAttribute = parser.getAttributeValue(null /*namespace*/,
96                                    CLASS_ATTRIBUTE);
97                            if (classAttribute == null) { // all activities
98                                useAllActivities = true;
99                            } else {
100                                if (activities == null) {
101                                    activities = new LinkedList<>();
102                                }
103                                activities.add(classAttribute);
104                            }
105                        } break;
106                    }
107                }
108                eventType = parser.next();
109            }
110        } catch (XmlPullParserException | IOException e) {
111            Log.w(CarLog.TAG_PACKAGE, "Resource not parsable [" + packageName + "]");
112            return null;
113        }
114        String[] activityStrings = null;
115        if (activities != null) {
116            activityStrings = activities.toArray(new String[activities.size()]);
117        }
118        return new CarAppMetadataInfo(useService, useAllActivities, activityStrings);
119    }
120
121    /**
122     * Returns the resource ID of the automotive application XML
123     * @throws NameNotFoundException
124     */
125    private static int getMetadataId(Context context, String packageName)
126            throws NameNotFoundException {
127        final PackageManager pm = context.getPackageManager();
128        ApplicationInfo appInfo;
129        appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA /*flags*/);
130        if (appInfo.metaData == null) {
131            return 0;
132        }
133        return appInfo.metaData.getInt(METADATA_ATTRIBUTE, 0);
134    }
135
136    /**
137     * App's metada for car application
138     */
139    public static class CarAppMetadataInfo {
140        /** has "service" use */
141        public final boolean useService;
142        /** has "activity" use with no specific class */
143        public final boolean useAllActivities;
144        /** has "activity" uses with specific classes */
145        public String[] activities;
146
147        private CarAppMetadataInfo(boolean useService, boolean useAllActivities,
148                String[] activities) {
149            this.useService = useService;
150            this.useAllActivities = useAllActivities;
151            this.activities = activities;
152        }
153    }
154}
155