/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tradefed.device; import com.android.tradefed.log.LogUtil.CLog; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parser for 'adb shell dumpsys package p' output. */ class DumpsysPackageParser { /** the text that marks the beginning of the hidden system packages section in output */ private static final String HIDDEN_SYSTEM_PACKAGES_PREFIX = "Hidden system packages:"; /** the text marking the start of a single package's output */ private static final String PACKAGE_START = "Package\\s\\["; /** package name + flags regex for 4.2 platforms and below where pkgFlags is a hex number */ private static final Pattern PKG_DATA_PATTERN = Pattern.compile( "^([\\w\\.]+)\\].*pkgFlags=0x([0-9a-fA-F]+)", Pattern.DOTALL); /** package name + flags regex for platforms newer than 4.2 where pkgFlags is a string */ private static final Pattern PKG_DATA_STRING_PATTERN = Pattern.compile( "^([\\w\\.]+)\\].*pkgFlags=\\[([\\w\\s]+)\\]", Pattern.DOTALL); /** package name regex for hidden system packages */ private static final Pattern HIDDEN_PKG_PATTERN = Pattern.compile("^([\\w\\.]+)\\]", Pattern.DOTALL); // numeric flag constants. Copied from // frameworks/base/core/java/android/content/pm/ApplicationInfo.java private static final int FLAG_UPDATED_SYSTEM_APP = 1 << 7; private static final int FLAG_SYSTEM = 1 << 0; // string flag constants. Used for newer platforms private static final String FLAG_UPDATED_SYSTEM_APP_TEXT = " UPDATED_SYSTEM_APP "; private static final String FLAG_SYSTEM_TEXT = " SYSTEM "; static class PackageInfo { final String packageName; final boolean isSystemApp; boolean isUpdatedSystemApp; PackageInfo(String pkgName, boolean systemApp, boolean updatedSystemApp) { packageName = pkgName; isSystemApp = systemApp; isUpdatedSystemApp = updatedSystemApp; } } @SuppressWarnings("serial") static class ParseException extends IOException { ParseException(String msg) { super(msg); } ParseException(String msg, Throwable t) { super(msg, t); } } /** * Parse package data from 'dumpsys package p' output from device. * * @param data the {@link String} data * @return a (@link DumpsysPackageParser} containing parsed data * @throws ParseException */ public static DumpsysPackageParser parse(String data) throws ParseException { DumpsysPackageParser p = new DumpsysPackageParser(); p.doParse(data); return p; } private Map<String, PackageInfo> mPkgInfoMap = new HashMap<String, PackageInfo>(); /** * Perform the parsing of the entire 'dumpsys package p' output. */ void doParse(String data) throws ParseException { if (data.length() == 0) { return; } // first find the hidden system package section, which is expected at end of output int hiddenPkgIndex = data.lastIndexOf(HIDDEN_SYSTEM_PACKAGES_PREFIX); if (hiddenPkgIndex == -1) { // didn't find hidden system package data. Mark index at end of data so remaining logic // just works hiddenPkgIndex = data.length() - 1; } String packageData = data.substring(0, hiddenPkgIndex); String hiddenPkgData = data.substring(hiddenPkgIndex, data.length() - 1); parsePackagesData(packageData); parseHiddenSystemPackages(hiddenPkgData); } /** * Parse the set of package data output */ private void parsePackagesData(String data) throws ParseException { String[] pkgTexts = data.split(PACKAGE_START); for (String pkgText : pkgTexts) { PackageInfo p = parsePackageData(pkgText); if (p != null) { mPkgInfoMap.put(p.packageName, p); } } } /** * Parse a single package's output */ PackageInfo parsePackageData(String pkgText) throws ParseException { // first try to parse the output using 'classic', numeric flag format PackageInfo p = parsePackageDataNumericFlags(pkgText); if (p == null) { // that didn't work. Maybe its the new improved flavor, with flags in string form p = parsePackageDataStringFlags(pkgText); } return p; } /** * Attempt to parse a {@link PackageInfo} from a single package's output assuming the numeric * package flag regex. * * @return the {@link PackageInfo} or <code>null</code> */ private PackageInfo parsePackageDataNumericFlags(String pkgText) throws ParseException { Matcher matcher = PKG_DATA_PATTERN.matcher(pkgText); if (matcher.find()) { String name = matcher.group(1); int flags = parseHexInt(matcher.group(2)); boolean isSystem = (flags & FLAG_SYSTEM) != 0; // note: FLAG_UPDATED_SYSTEM_APP never seems to be set. Rely on parsing hidden system // packages boolean isUpdatedSystem = (flags & FLAG_UPDATED_SYSTEM_APP) != 0; return new PackageInfo(name, isSystem, isUpdatedSystem); } return null; } /** * Attempt to parse a {@link PackageInfo} from a single package's output assuming the string * package flag regex. * * @return the {@link PackageInfo} or <code>null</code> */ private PackageInfo parsePackageDataStringFlags(String pkgText) { Matcher matcher = PKG_DATA_STRING_PATTERN.matcher(pkgText); if (matcher.find()) { String name = matcher.group(1); String flagString = matcher.group(2); boolean isSystem = flagString.contains(FLAG_SYSTEM_TEXT); boolean isUpdatedSystem = flagString.contains(FLAG_UPDATED_SYSTEM_APP_TEXT); return new PackageInfo(name, isSystem, isUpdatedSystem); } return null; } /** * Convert given hexadecimal string to an int. * * @throws ParseException if failed to parse */ private int parseHexInt(String s) throws ParseException { try { return Integer.parseInt(s, 16); } catch (NumberFormatException e) { throw new ParseException(String.format("Unexpected flags value %s", s), e); } } /** * Parse the entire hidden system packages text. */ private void parseHiddenSystemPackages(String s) { String[] pkgTexts = s.split(PACKAGE_START); for (String pkgText : pkgTexts) { Matcher matcher = HIDDEN_PKG_PATTERN.matcher(pkgText); if (matcher.find()) { String name = matcher.group(1); PackageInfo p = mPkgInfoMap.get(name); if (p != null) { p.isUpdatedSystemApp = true; } else { CLog.w("Failed to find package info for hidden system package %s", name); } } } } /** * @return the parsed {@link PackageInfo}. */ public Collection<PackageInfo> getPackages() { return mPkgInfoMap.values(); } }