1 /*
2  * Copyright (C) 2018 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 com.android.cts.releaseparser;
18 
19 import com.android.cts.releaseparser.ReleaseProto.*;
20 import com.google.protobuf.TextFormat;
21 
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 public class AndroidManifestParser extends FileParser {
34     private static Pattern pattern_element = Pattern.compile("E: (\\S*) ");
35     private static Pattern pattern_item =
36             Pattern.compile("A: (?:android:)?(\\w*)(?:\\S*)?=(?:\\(\\S*\\s*\\S*\\))?(\\S*)");
37     private AppInfo.Builder mAppInfoBuilder;
38     private String mElementName;
39     private BufferedReader mReader;
40     private Map<String, String> mProperties;
41     private UsesFeature.Builder mUsesFeatureBuilder;
42     private UsesLibrary.Builder mUsesLibraryBuilder;
43 
AndroidManifestParser(File file)44     public AndroidManifestParser(File file) {
45         super(file);
46     }
47 
48     @Override
getType()49     public Entry.EntryType getType() {
50         return Entry.EntryType.APK;
51     }
52 
getAppInfo()53     public AppInfo getAppInfo() {
54         return getAppInfoBuilder().build();
55     }
56 
getAppInfoBuilder()57     public AppInfo.Builder getAppInfoBuilder() {
58         if (mAppInfoBuilder == null) {
59             prase();
60         }
61         return mAppInfoBuilder;
62     }
63 
prase()64     private void prase() {
65         mAppInfoBuilder = AppInfo.newBuilder();
66         processManifest();
67     }
68 
69     // build cmd list as: "aapt", "d", "xmltree", fileName, "AndroidManifest.xml"
getAaptCmds(String file)70     private List<String> getAaptCmds(String file) {
71         List<String> cmds = new ArrayList<>();
72         cmds.add("aapt");
73         cmds.add("d");
74         cmds.add("xmltree");
75         cmds.add(file);
76         cmds.add("AndroidManifest.xml");
77         return cmds;
78     }
79 
processManifest()80     private void processManifest() {
81         try {
82             // ToDo prasing as android/frameworks/base/tools/aapt/Resource.cpp parsePackage()
83             Process process = new ProcessBuilder(getAaptCmds(mFile.getAbsolutePath())).start();
84             mReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
85             parseElement();
86         } catch (Exception ex) {
87             System.err.println("Failed to aapt d badging " + mFile.getAbsolutePath());
88             ex.printStackTrace();
89         }
90     }
91 
parseElement()92     private void parseElement() {
93         String eleName;
94         Matcher matcher;
95         String line;
96         mProperties = new HashMap<>();
97         int indent;
98 
99         try {
100             while ((line = mReader.readLine()) != null) {
101                 eleName = getElementName(line);
102                 if (eleName != null) {
103                     mElementName = eleName;
104                     parseElementItem();
105                 }
106             }
107             mAppInfoBuilder.putAllProperties(mProperties);
108         } catch (Exception ex) {
109             System.err.println(
110                     "Failed to aapt & parse AndroidManifest.xml from: " + mFile.getAbsolutePath());
111             ex.printStackTrace();
112         }
113     }
114 
parseElementItem()115     private void parseElementItem() throws IOException {
116         String eleName;
117         Matcher matcher;
118         String line;
119         int indent;
120         while ((line = mReader.readLine()) != null) {
121             indent = parseItem(line);
122             if (indent == -1) {
123                 eleName = getElementName(line);
124                 if (eleName == null) {
125                     break;
126                 }
127 
128                 switch (mElementName) {
129                     case "uses-feature":
130                         mAppInfoBuilder.addUsesFeatures(mUsesFeatureBuilder);
131                         mUsesFeatureBuilder = null;
132                         break;
133                     case "uses-library":
134                         mAppInfoBuilder.addUsesLibraries(mUsesLibraryBuilder);
135                         mUsesLibraryBuilder = null;
136                         break;
137                 }
138                 mElementName = eleName;
139             }
140         }
141     }
142 
parseItem(String line)143     private int parseItem(String line) {
144         String key;
145         String value;
146         Matcher matcher;
147         int indent = line.indexOf("A: ");
148         if (indent != -1) {
149             matcher = pattern_item.matcher(line);
150             if (matcher.find()) {
151                 int processed = 0;
152                 key = matcher.group(1);
153                 value = matcher.group(2).replace("\"", "");
154 
155                 switch (mElementName) {
156                     case "manifest":
157                         if (key.equals("package")) {
158                             mAppInfoBuilder.setPackageName(value);
159                             processed = 1;
160                             break;
161                         }
162                     case "uses-sdk":
163                     case "application":
164                     case "supports-screens":
165                         mProperties.put(key, value);
166                         processed = 1;
167                         break;
168                     case "original-package":
169                         if (key.equals("name")) {
170                             mProperties.put(mElementName, value);
171                             processed = 1;
172                         }
173                         break;
174                     case "uses-permission":
175                         if (key.equals("name")) {
176                             mAppInfoBuilder.addUsesPermissions(value);
177                             processed = 1;
178                         }
179                         break;
180                     case "activity":
181                         if (key.equals("name")) {
182                             mAppInfoBuilder.addActivities(value);
183                             processed = 1;
184                         }
185                         break;
186                     case "service":
187                         if (key.equals("name")) {
188                             mAppInfoBuilder.addServices(value);
189                             processed = 1;
190                         }
191                         break;
192                     case "provider":
193                         if (key.equals("name")) {
194                             mAppInfoBuilder.addProviders(value);
195                             processed = 1;
196                         }
197                         break;
198                     case "uses-feature":
199                         putFeature(key, value);
200                         processed = 1;
201                         break;
202                     case "uses-library":
203                         putLibrary(key, value);
204                         processed = 1;
205                         break;
206                     default:
207                 }
208                 System.out.println(
209                         String.format("%s,%s,%s,%d", mElementName, key, value, processed));
210             }
211         }
212         return indent;
213     }
214 
putFeature(String key, String value)215     private void putFeature(String key, String value) {
216         if (mUsesFeatureBuilder == null) {
217             mUsesFeatureBuilder = UsesFeature.newBuilder();
218         }
219         switch (key) {
220             case "name":
221                 mUsesFeatureBuilder.setName(value);
222             case "required":
223                 mUsesFeatureBuilder.setRequired(value);
224         }
225     }
226 
putLibrary(String key, String value)227     private void putLibrary(String key, String value) {
228         if (mUsesLibraryBuilder == null) {
229             mUsesLibraryBuilder = UsesLibrary.newBuilder();
230         }
231         switch (key) {
232             case "name":
233                 mUsesLibraryBuilder.setName(value);
234             case "required":
235                 mUsesLibraryBuilder.setRequired(value);
236         }
237     }
238 
getElementName(String line)239     private String getElementName(String line) {
240         String name;
241         Matcher matcher;
242         int indent = line.indexOf("E: ");
243         if (indent != -1) {
244             matcher = pattern_element.matcher(line);
245             if (matcher.find()) {
246                 name = matcher.group(1);
247                 return name;
248             }
249         }
250         return null;
251     }
252 
253     private static final String USAGE_MESSAGE =
254             "Usage: java -jar releaseparser.jar com.android.cts.releaseparser.ApkParser [-options] <path> [args...]\n"
255                     + "           to prase an APK for API\n"
256                     + "Options:\n"
257                     + "\t-i PATH\t APK path \n";
258 
259     /** Get the argument or print out the usage and exit. */
printUsage()260     private static void printUsage() {
261         System.out.printf(USAGE_MESSAGE);
262         System.exit(1);
263     }
264 
265     /** Get the argument or print out the usage and exit. */
getExpectedArg(String[] args, int index)266     private static String getExpectedArg(String[] args, int index) {
267         if (index < args.length) {
268             return args[index];
269         } else {
270             printUsage();
271             return null; // Never will happen because printUsage will call exit(1)
272         }
273     }
274 
main(String[] args)275     public static void main(String[] args) throws IOException {
276         String apkFileName = null;
277 
278         for (int i = 0; i < args.length; i++) {
279             if (args[i].startsWith("-")) {
280                 if ("-i".equals(args[i])) {
281                     apkFileName = getExpectedArg(args, ++i);
282                 }
283             }
284         }
285 
286         if (apkFileName == null) {
287             printUsage();
288         }
289 
290         File apkFile = new File(apkFileName);
291         AndroidManifestParser manifestParser = new AndroidManifestParser(apkFile);
292         System.out.println();
293         System.out.println(TextFormat.printToString(manifestParser.getAppInfo()));
294     }
295 }
296