1 /*
2  * Copyright (C) 2013 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.tradefed.device;
18 
19 import com.android.ddmlib.MultiLineReceiver;
20 import com.android.tradefed.build.BuildSerializedVersion;
21 import com.android.tradefed.log.LogUtil.CLog;
22 
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28 
29 /**
30  * Parser for 'adb shell dumpsys package' output.
31  */
32 class DumpsysPackageReceiver extends MultiLineReceiver {
33 
34     /** the text that marks the beginning of the hidden system packages section in output */
35     private static final String HIDDEN_SYSTEM_PACKAGES_PREFIX = "Hidden system packages:";
36 
37     /** regex for marking the start of a single package's output */
38     private static final Pattern PACKAGE_PATTERN = Pattern.compile("Package\\s\\[([\\w\\.]+)\\]");
39     /** regex for marking the start of a single user's output inside a package's output */
40     private static final Pattern USER_PATTERN = Pattern.compile("User\\s(\\d+):");
41 
42     static class ParseException extends IOException {
43         private static final long serialVersionUID = BuildSerializedVersion.VERSION;
44 
ParseException(String msg)45         ParseException(String msg) {
46             super(msg);
47         }
48 
ParseException(String msg, Throwable t)49         ParseException(String msg, Throwable t) {
50             super(msg, t);
51         }
52     }
53 
54     /**
55      * State handling interface for parsing output. Using GoF state design pattern.
56      */
57     private interface ParserState {
parse(String line)58         ParserState parse(String line) throws ParseException;
59     }
60 
61     /**
62      * Initial state of package parser, where its looking for start of package to parse.
63      */
64     private class PackagesParserState implements ParserState {
65 
66         /**
67          * {@inheritDoc}
68          */
69         @Override
parse(String line)70         public ParserState parse(String line) throws ParseException {
71             Matcher matcher = PACKAGE_PATTERN.matcher(line);
72             if (matcher.find()) {
73                 String name = matcher.group(1);
74                 return new PackageParserState(name);
75             }
76             return this;
77         }
78     }
79 
80     /**
81      * Parser for a single package's data.
82      * <p/>
83      * Expected pattern is:
84      * Package: [com.foo]
85      *   key=value
86      *   key2=value2
87      */
88     private class PackageParserState implements ParserState {
89 
90         private PackageInfo mPkgInfo;
91 
92         /**
93          * @param name
94          */
PackageParserState(String name)95         public PackageParserState(String name) {
96             mPkgInfo = new PackageInfo(name);
97             addPackage(name, mPkgInfo);
98         }
99 
100         /**
101          * {@inheritDoc}
102          */
103         @Override
parse(String line)104         public ParserState parse(String line) throws ParseException {
105             // first look if we've moved on to another package
106             Matcher matcher = PACKAGE_PATTERN.matcher(line);
107             if (matcher.find()) {
108                 String name = matcher.group(1);
109                 return new PackageParserState(name);
110             }
111             if (line.startsWith(HIDDEN_SYSTEM_PACKAGES_PREFIX)) {
112                 // done parsing packages, now parse hidden packages
113                 return new HiddenPackagesParserState();
114             }
115             Matcher userStateMatcher = USER_PATTERN.matcher(line);
116             if (userStateMatcher.find()) {
117                 int userId = Integer.parseInt(userStateMatcher.group(1));
118                 return new PerUserPackageParserState(mPkgInfo.getPackageName(), userId);
119             }
120 
121             parseAttributes(line);
122             return this;
123         }
124 
parseAttributes(String line)125         private void parseAttributes(String line) {
126             String[] prop = line.split("=");
127             if (prop.length == 2) {
128                 mPkgInfo.addAttribute(prop[0], prop[1]);
129             } else if (prop.length > 2) {
130                 // multiple props on one line. Split by both whitespace and =
131                 String[] vn = line.split(" |=");
132                 if (vn.length % 2 != 0) {
133                     // improper format, ignore
134                     return;
135                 }
136                 for (int i=0; i < vn.length; i = i + 2) {
137                     mPkgInfo.addAttribute(vn[i], vn[i+1]);
138                 }
139             }
140 
141         }
142     }
143 
144     /**
145      * State of package parser where its looking for start of hidden packages to parse.
146      */
147     private class HiddenPackagesParserState implements ParserState {
148 
149         /**
150          * {@inheritDoc}
151          */
152         @Override
parse(String line)153         public ParserState parse(String line) throws ParseException {
154             Matcher matcher = PACKAGE_PATTERN.matcher(line);
155             if (matcher.find()) {
156                 String name = matcher.group(1);
157                 return new HiddenPackageParserState(name);
158             }
159             return this;
160         }
161     }
162 
163     /**
164      * Parser for a single package's data
165      */
166     private class HiddenPackageParserState implements ParserState {
167 
168         private PackageInfo mPkgInfo;
169 
170         /**
171          * @param name
172          * @throws ParseException
173          */
HiddenPackageParserState(String name)174         public HiddenPackageParserState(String name) throws ParseException {
175             mPkgInfo = mPkgInfoMap.get(name);
176             if (mPkgInfo == null) {
177                 throw new ParseException(String.format(
178                         "could not find package for hidden package %s", name));
179             }
180             mPkgInfo.setIsUpdatedSystemApp(true);
181         }
182 
183         /**
184          * {@inheritDoc}
185          */
186         @Override
parse(String line)187         public ParserState parse(String line) throws ParseException {
188             Matcher matcher = PACKAGE_PATTERN.matcher(line);
189             if (matcher.find()) {
190                 String name = matcher.group(1);
191                 return new HiddenPackageParserState(name);
192             }
193             return this;
194         }
195     }
196 
197     private class PerUserPackageParserState implements ParserState {
198         private final PackageInfo mPkgInfo;
199         private final int mUserId;
200 
PerUserPackageParserState(String name, int userId)201         public PerUserPackageParserState(String name, int userId) throws ParseException {
202             mPkgInfo = mPkgInfoMap.get(name);
203             if (mPkgInfo == null) {
204                 throw new ParseException(
205                         String.format("could not find package for hidden package %s", name));
206             }
207             mUserId = userId;
208         }
209 
210         @Override
parse(String line)211         public ParserState parse(String line) throws ParseException {
212             Matcher matcher = PACKAGE_PATTERN.matcher(line);
213             if (matcher.find()) {
214                 String name = matcher.group(1);
215                 return new PackageParserState(name);
216             }
217             Matcher userStateMatcher = USER_PATTERN.matcher(line);
218             if (userStateMatcher.find()) {
219                 int userId = Integer.parseInt(userStateMatcher.group(1));
220                 return new PerUserPackageParserState(mPkgInfo.getPackageName(), userId);
221             }
222             parseAttributes(line);
223             return this;
224         }
225 
parseAttributes(String line)226         private void parseAttributes(String line) {
227             String[] prop = line.split("=");
228             if (prop.length == 2) {
229                 mPkgInfo.addPerUserAttribute(mUserId, prop[0], prop[1]);
230             }
231         }
232     }
233 
234     private Map<String, PackageInfo> mPkgInfoMap = new HashMap<String, PackageInfo>();
235 
236     private ParserState mCurrentState = new PackagesParserState();
237 
238     private boolean mCancelled = false;
239 
addPackage(String name, PackageInfo pkgInfo)240     void addPackage(String name, PackageInfo pkgInfo) {
241         mPkgInfoMap.put(name, pkgInfo);
242     }
243 
244     /**
245      * @return the parsed {@link PackageInfo}s as a map of package name to {@link PackageInfo}.
246      */
getPackages()247     public Map<String, PackageInfo> getPackages() {
248         return mPkgInfoMap;
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
isCancelled()255     public boolean isCancelled() {
256         return mCancelled ;
257     }
258 
259     /**
260      * {@inheritDoc}
261      */
262     @Override
processNewLines(String[] lines)263     public void processNewLines(String[] lines) {
264         try {
265             for (String line : lines) {
266                 mCurrentState = mCurrentState.parse(line);
267             }
268         } catch (ParseException e) {
269             CLog.e(e);
270             mCancelled = true;
271         }
272     }
273 }
274