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 { 41 ParseException(String msg) { 42 super(msg); 43 } 44 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 { 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 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 */ 91 public PackageParserState(String name) { 92 mPkgInfo = new PackageInfo(name); 93 addPackage(name, mPkgInfo); 94 } 95 96 /** 97 * {@inheritDoc} 98 */ 99 @Override 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 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 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 */ 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 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 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 */ 201 public Map<String, PackageInfo> getPackages() { 202 return mPkgInfoMap; 203 } 204 205 /** 206 * {@inheritDoc} 207 */ 208 @Override 209 public boolean isCancelled() { 210 return mCancelled ; 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 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