/*
* Copyright (C) 2014 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.builder.core;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.process.BaseProcessOutputHandler;
import com.android.ide.common.process.CachedProcessOutputHandler;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.utils.SdkUtils;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class to parse an APK with aapt to gather information
*/
public class ApkInfoParser {
private static final Pattern PATTERN = Pattern.compile(
"^package: name='([^']+)' versionCode='([0-9]*)' versionName='([^']*)'.*$");
@NonNull
private final File mAaptFile;
@NonNull
private final ProcessExecutor mProcessExecutor;
/**
* Information about an APK
*/
public static final class ApkInfo {
@NonNull
private final String mPackageName;
@Nullable
private final Integer mVersionCode;
@Nullable
private final String mVersionName;
private ApkInfo(@NonNull String packageName, Integer versionCode, String versionName) {
mPackageName = packageName;
mVersionCode = versionCode;
mVersionName = versionName;
}
@NonNull
public String getPackageName() {
return mPackageName;
}
@Nullable
public Integer getVersionCode() {
return mVersionCode;
}
@Nullable
public String getVersionName() {
return mVersionName;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("packageName", mPackageName)
.add("versionCode", mVersionCode)
.add("versionName", mVersionName)
.toString();
}
}
/**
* Constructs a new parser
* @param aaptFile aapt file to use to gather the info
* @param processExecutor a process Executor to call aapt
*/
public ApkInfoParser(
@NonNull File aaptFile,
@NonNull ProcessExecutor processExecutor) {
mAaptFile = aaptFile;
mProcessExecutor = processExecutor;
}
/**
* Computes and returns the info for an APK
* @param apkFile the APK to parse
* @return a non-null ApkInfo object.
* @throws ProcessException
*/
@NonNull
public ApkInfo parseApk(@NonNull File apkFile)
throws ProcessException {
if (!mAaptFile.isFile()) {
throw new IllegalStateException(
"aapt is missing from location: " + mAaptFile.getAbsolutePath());
}
return getApkInfo(getAaptOutput(apkFile));
}
/**
* Parses the aapt output and returns an ApkInfo object.
* @param aaptOutput the aapt output as a list of lines.
* @return an ApkInfo object.
*/
@VisibleForTesting
@NonNull
static ApkInfo getApkInfo(@NonNull List<String> aaptOutput) {
String pkgName = null, versionCode = null, versionName = null;
for (String line : aaptOutput) {
Matcher m = PATTERN.matcher(line);
if (m.matches()) {
pkgName = m.group(1);
versionCode = m.group(2);
versionName = m.group(3);
break;
}
}
if (pkgName == null) {
throw new RuntimeException("Failed to find apk information with aapt");
}
Integer intVersionCode = null;
try {
intVersionCode = Integer.parseInt(versionCode);
} catch(NumberFormatException ignore) {
// leave the version code as null.
}
return new ApkInfo(pkgName, intVersionCode, versionName);
}
/**
* Returns the aapt output.
* @param apkFile the apk file to call aapt on.
* @return the output as a list of files.
* @throws ProcessException
*/
@NonNull
private List<String> getAaptOutput(@NonNull File apkFile)
throws ProcessException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(mAaptFile);
builder.addArgs("dump", "badging", apkFile.getPath());
CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();
mProcessExecutor.execute(
builder.createProcess(), processOutputHandler)
.rethrowFailure().assertNormalExitValue();
BaseProcessOutputHandler.BaseProcessOutput output = processOutputHandler.getProcessOutput();
return Splitter.on(SdkUtils.getLineSeparator()).splitToList(output.getStandardOutputAsString());
}
}