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