1 /* 2 * Copyright (C) 2012 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 package com.android.tradefed.util; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import com.google.common.base.Strings; 21 22 import java.io.File; 23 import java.util.ArrayList; 24 import java.util.List; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 28 /** 29 * Class that extracts info from apk by parsing output of 'aapt dump badging'. 30 * <p/> 31 * aapt must be on PATH 32 */ 33 public class AaptParser { 34 private static final Pattern PKG_PATTERN = Pattern.compile( 35 "^package:\\s+name='(.*?)'\\s+versionCode='(\\d*)'\\s+versionName='(.*?)'.*$", 36 Pattern.MULTILINE); 37 private static final Pattern LABEL_PATTERN = Pattern.compile( 38 "^application-label:'(.+?)'.*$", 39 Pattern.MULTILINE); 40 private static final Pattern SDK_PATTERN = Pattern.compile( 41 "^sdkVersion:'(\\d+)'", Pattern.MULTILINE); 42 private static final Pattern TARGET_SDK_PATTERN = 43 Pattern.compile("^targetSdkVersion:'(\\d+)'", Pattern.MULTILINE); 44 /** 45 * 1. Patterns for native code are not always present, so the list may stay empty; 2. There may 46 * be more than two native-codes for APKs built by Soong; 3. Java regular expressions cannot be 47 * captured for each group in a repeated group, so hard-code it for now to handle up to ten 48 * native-codes. 49 */ 50 private static final int MAX_NUM_NATIVE_CODE = 10; 51 52 private static final Pattern NATIVE_CODE_PATTERN = 53 Pattern.compile( 54 "native-code: '(.*?)'" + Strings.repeat("( '.*?')?", MAX_NUM_NATIVE_CODE - 1)); 55 56 private static final Pattern REQUEST_LEGACY_STORAGE_PATTERN = 57 Pattern.compile("requestLegacyExternalStorage.*=\\(.*\\)(.*)", Pattern.MULTILINE); 58 59 private static final Pattern ALT_NATIVE_CODE_PATTERN = 60 Pattern.compile("alt-native-code: '(.*)'"); 61 private static final Pattern USES_PERMISSION_MANAGE_EXTERNAL_STORAGE_PATTERN = 62 Pattern.compile( 63 "uses-permission: name='android\\.permission\\.MANAGE_EXTERNAL_STORAGE'"); 64 private static final int AAPT_TIMEOUT_MS = 60000; 65 private static final int INVALID_SDK = -1; 66 67 /** 68 * Enum of options for AAPT version used to parse APK files. 69 */ 70 public static enum AaptVersion { 71 AAPT { 72 @Override dumpBadgingCommand(File apkFile)73 public String[] dumpBadgingCommand(File apkFile) { 74 return new String[] {"aapt", "dump", "badging", apkFile.getAbsolutePath()}; 75 } 76 77 @Override dumpXmlTreeCommand(File apkFile)78 public String[] dumpXmlTreeCommand(File apkFile) { 79 return new String[] { 80 "aapt", "dump", "xmltree", apkFile.getAbsolutePath(), "AndroidManifest.xml" 81 }; 82 } 83 }, 84 85 AAPT2 { 86 @Override dumpBadgingCommand(File apkFile)87 public String[] dumpBadgingCommand(File apkFile) { 88 return new String[] {"aapt2", "dump", "badging", apkFile.getAbsolutePath()}; 89 } 90 91 @Override dumpXmlTreeCommand(File apkFile)92 public String[] dumpXmlTreeCommand(File apkFile) { 93 return new String[] { 94 "aapt2", 95 "dump", 96 "xmltree", 97 apkFile.getAbsolutePath(), 98 "--file", 99 "AndroidManifest.xml" 100 }; 101 } 102 }; 103 dumpBadgingCommand(File apkFile)104 public abstract String[] dumpBadgingCommand(File apkFile); 105 dumpXmlTreeCommand(File apkFile)106 public abstract String[] dumpXmlTreeCommand(File apkFile); 107 } 108 109 private String mPackageName; 110 private String mVersionCode; 111 private String mVersionName; 112 private List<String> mNativeCode = new ArrayList<>(); 113 private String mLabel; 114 private int mSdkVersion = INVALID_SDK; 115 private int mTargetSdkVersion = 10000; 116 private boolean mRequestLegacyStorage = false; 117 private boolean mUsesPermissionManageExternalStorage; 118 119 // @VisibleForTesting AaptParser()120 AaptParser() { 121 } 122 parse(String aaptOut)123 boolean parse(String aaptOut) { 124 //CLog.e(aaptOut); 125 Matcher m = PKG_PATTERN.matcher(aaptOut); 126 if (m.find()) { 127 mPackageName = m.group(1); 128 mLabel = mPackageName; 129 mVersionCode = m.group(2); 130 mVersionName = m.group(3); 131 m = LABEL_PATTERN.matcher(aaptOut); 132 if (m.find()) { 133 mLabel = m.group(1); 134 } 135 m = SDK_PATTERN.matcher(aaptOut); 136 if (m.find()) { 137 mSdkVersion = Integer.parseInt(m.group(1)); 138 } 139 m = TARGET_SDK_PATTERN.matcher(aaptOut); 140 if (m.find()) { 141 mTargetSdkVersion = Integer.parseInt(m.group(1)); 142 } 143 m = NATIVE_CODE_PATTERN.matcher(aaptOut); 144 if (m.find()) { 145 for (int i = 1; i <= m.groupCount(); i++) { 146 if (m.group(i) != null) { 147 mNativeCode.add(m.group(i).replace("'", "").trim()); 148 } 149 } 150 } 151 m = ALT_NATIVE_CODE_PATTERN.matcher(aaptOut); 152 if (m.find()) { 153 for (int i = 1; i <= m.groupCount(); i++) { 154 if (m.group(i) != null) { 155 mNativeCode.add(m.group(i).replace("'", "")); 156 } 157 } 158 } 159 m = USES_PERMISSION_MANAGE_EXTERNAL_STORAGE_PATTERN.matcher(aaptOut); 160 mUsesPermissionManageExternalStorage = m.find(); 161 return true; 162 } 163 CLog.e("Failed to parse package and version info from 'aapt dump badging'. stdout: '%s'", 164 aaptOut); 165 return false; 166 } 167 168 /** 169 * Parse info from the apk. 170 * 171 * @param apkFile the apk file 172 * @return the {@link AaptParser} or <code>null</code> if failed to extract the information 173 */ parse(File apkFile)174 public static AaptParser parse(File apkFile) { 175 return parse(apkFile, AaptVersion.AAPT); 176 } 177 178 /** 179 * Parse info from the apk. 180 * 181 * @param apkFile the apk file 182 * @param aaptVersion the aapt version 183 * @return the {@link AaptParser} or <code>null</code> if failed to extract the information 184 */ parse(File apkFile, AaptVersion aaptVersion)185 public static AaptParser parse(File apkFile, AaptVersion aaptVersion) { 186 CommandResult result = 187 RunUtil.getDefault() 188 .runTimedCmdRetry( 189 AAPT_TIMEOUT_MS, 0L, 2, aaptVersion.dumpBadgingCommand(apkFile)); 190 191 String stderr = result.getStderr(); 192 if (stderr != null && !stderr.isEmpty()) { 193 CLog.e("%s dump badging stderr: %s", toolName(aaptVersion), stderr); 194 } 195 AaptParser p = new AaptParser(); 196 if (!CommandStatus.SUCCESS.equals(result.getStatus()) || !p.parse(result.getStdout())) { 197 CLog.e( 198 "Failed to run %s on %s. stdout: %s", 199 toolName(aaptVersion), apkFile.getAbsoluteFile(), result.getStdout()); 200 return null; 201 } 202 result = 203 RunUtil.getDefault() 204 .runTimedCmdRetry( 205 AAPT_TIMEOUT_MS, 206 0L, 207 2, 208 aaptVersion.dumpXmlTreeCommand(apkFile)); 209 210 stderr = result.getStderr(); 211 if (stderr != null && !stderr.isEmpty()) { 212 CLog.e("%s dump xmltree AndroidManifest.xml stderr: %s", toolName(aaptVersion), stderr); 213 } 214 215 if (!CommandStatus.SUCCESS.equals(result.getStatus()) 216 || !p.parseXmlTree(result.getStdout())) { 217 CLog.e( 218 "Failed to run %s on %s. stdout: %s", 219 toolName(aaptVersion), apkFile.getAbsoluteFile(), result.getStdout()); 220 return null; 221 } 222 return p; 223 } 224 parseXmlTree(String aaptOut)225 boolean parseXmlTree(String aaptOut) { 226 Matcher m = REQUEST_LEGACY_STORAGE_PATTERN.matcher(aaptOut); 227 if (m.find()) { 228 // 0xffffffff is true and 0x0 is false 229 mRequestLegacyStorage = m.group(1).equals("0xffffffff"); 230 } 231 // REQUEST_LEGACY_STORAGE_PATTERN may or may not be present 232 return true; 233 } 234 getPackageName()235 public String getPackageName() { 236 return mPackageName; 237 } 238 getVersionCode()239 public String getVersionCode() { 240 return mVersionCode; 241 } 242 getVersionName()243 public String getVersionName() { 244 return mVersionName; 245 } 246 getNativeCode()247 public List<String> getNativeCode() { 248 return mNativeCode; 249 } 250 getLabel()251 public String getLabel() { 252 return mLabel; 253 } 254 getSdkVersion()255 public int getSdkVersion() { 256 return mSdkVersion; 257 } 258 getTargetSdkVersion()259 public int getTargetSdkVersion() { 260 return mTargetSdkVersion; 261 } 262 263 /** 264 * Check if the app is requesting legacy storage. 265 * 266 * @return boolean return true if requestLegacyExternalStorage is true in AndroidManifest.xml 267 */ isRequestingLegacyStorage()268 public boolean isRequestingLegacyStorage() { 269 return mRequestLegacyStorage; 270 } 271 isUsingPermissionManageExternalStorage()272 public boolean isUsingPermissionManageExternalStorage() { 273 return mUsesPermissionManageExternalStorage; 274 } 275 toolName(AaptVersion version)276 private static String toolName(AaptVersion version) { 277 return version.name().toLowerCase(); 278 } 279 } 280