blob: 3850917ae42cea92e47873e5cc7aad578aba1c13 [file] [log] [blame]
/*
* 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.ide.common.build;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.FilterData;
import com.android.build.OutputFile;
import com.android.build.VariantOutput;
import com.android.builder.testing.api.DeviceConfigProvider;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.resources.Density;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Helper class to help with installation of multi-output variants.
*/
public class SplitOutputMatcher {
/**
* Determines and return the list of APKs to use based on given device density and abis.
*
* if there are pure splits, use the split-select tool otherwise revert to store logic.
*
* @param processExecutor an executor to execute native processes.
* @param splitSelectExe the split select tool optionally.
* @param deviceConfigProvider the device configuration.
* @param outputs the tested variant outpts.
* @param variantAbiFilters a list of abi filters applied to the variant. This is used in place
* of the outputs, if there is a single output with no abi filters.
* If the list is null, then the variant does not restrict ABI
* packaging.
* @return the list of APK files to install.
* @throws ProcessException
*/
@NonNull
public static List<File> computeBestOutput(
@NonNull ProcessExecutor processExecutor,
@Nullable File splitSelectExe,
@NonNull DeviceConfigProvider deviceConfigProvider,
@NonNull List<? extends VariantOutput> outputs,
@Nullable Collection<String> variantAbiFilters) throws ProcessException {
// build the list of APKs.
List<String> splitApksPath = new ArrayList<String>();
OutputFile mainApk = null;
for (VariantOutput output : outputs) {
for (OutputFile outputFile : output.getOutputs()) {
if (!outputFile.getOutputFile().getAbsolutePath().equals(
output.getMainOutputFile().getOutputFile().getAbsolutePath())) {
splitApksPath.add(outputFile.getOutputFile().getAbsolutePath());
}
}
mainApk = output.getMainOutputFile();
}
if (mainApk == null) {
throw new RuntimeException(
"Cannot retrieve the main APK from variant outputs");
}
if (splitApksPath.isEmpty()) {
List<File> apkFiles = new ArrayList<File>();
// now look for a matching output file
List<OutputFile> outputFiles = SplitOutputMatcher.computeBestOutput(
outputs,
variantAbiFilters,
deviceConfigProvider.getDensity(),
deviceConfigProvider.getAbis());
for (OutputFile outputFile : outputFiles) {
apkFiles.add(outputFile.getOutputFile());
}
return apkFiles;
} else {
if (splitSelectExe == null) {
throw new RuntimeException(
"Pure splits installation requires build tools 22 or above");
}
return computeBestOutput(processExecutor, splitSelectExe, deviceConfigProvider,
mainApk.getOutputFile(), splitApksPath);
}
}
/**
* Determines and return the list of APKs to use based on given device density and abis.
*
* if there are pure splits, use the split-select tool otherwise revert to store logic.
*
* @param processExecutor an executor to execute native processes.
* @param splitSelectExe the split select tool optionally.
* @param deviceConfigProvider the device configuration.
* @param mainApk the main apk file.
* @param splitApksPath the list of split apks path.
* @return the list of APK files to install.
* @throws ProcessException
*/
@NonNull
public static List<File> computeBestOutput(
@NonNull ProcessExecutor processExecutor,
@NonNull File splitSelectExe,
@NonNull DeviceConfigProvider deviceConfigProvider,
@NonNull File mainApk,
@NonNull Collection<String> splitApksPath) throws ProcessException {
// build the list of APKs.
if (splitApksPath.isEmpty()) {
return ImmutableList.of(mainApk);
} else {
List<File> apkFiles = new ArrayList<File>();
Set<String> resultApksPath = new HashSet<String>();
for (String abi : deviceConfigProvider.getAbis()) {
resultApksPath.addAll(SplitSelectTool.splitSelect(
processExecutor,
splitSelectExe,
deviceConfigProvider.getConfigFor(abi),
mainApk.getAbsolutePath(),
splitApksPath));
}
for (String resultApkPath : resultApksPath) {
apkFiles.add(new File(resultApkPath));
}
// and add back the main APK.
apkFiles.add(mainApk);
return apkFiles;
}
}
/**
* Determines and return the list of APKs to use based on given device density and abis.
*
* This uses the same logic as the store, using two passes:
* First, find all the compatible outputs.
* Then take the one with the highest versionCode.
*
* @param outputs the outputs to choose from.
* @param variantAbiFilters a list of abi filters applied to the variant. This is used in place
* of the outputs, if there is a single output with no abi filters.
* If the list is null, then the variant does not restrict ABI
* packaging.
* @param deviceDensity the density of the device.
* @param deviceAbis a list of ABIs supported by the device.
* @return the list of APKs to install or null if none are compatible.
*/
@NonNull
public static List<OutputFile> computeBestOutput(
@NonNull List<? extends VariantOutput> outputs,
@Nullable Collection<String> variantAbiFilters,
int deviceDensity,
@NonNull Collection<String> deviceAbis) {
Density densityEnum = Density.getEnum(deviceDensity);
String densityValue;
if (densityEnum == null) {
densityValue = null;
} else {
densityValue = densityEnum.getResourceValue();
}
// gather all compatible matches.
Set<VariantOutput> matches = new HashSet<VariantOutput>();
// find a matching output.
for (VariantOutput variantOutput : outputs) {
for (OutputFile output : variantOutput.getOutputs()) {
String densityFilter = getFilter(output, OutputFile.DENSITY);
String abiFilter = getFilter(output, OutputFile.ABI);
if (densityFilter != null && !densityFilter.equals(densityValue)) {
continue;
}
if (abiFilter != null && !deviceAbis.contains(abiFilter)) {
continue;
}
// variantOutput can be added several times to matches.
matches.add(variantOutput);
}
}
if (matches.isEmpty()) {
return ImmutableList.of();
}
VariantOutput match = Collections.max(matches, new Comparator<VariantOutput>() {
@Override
public int compare(VariantOutput splitOutput, VariantOutput splitOutput2) {
return splitOutput.getVersionCode() - splitOutput2.getVersionCode();
}
});
OutputFile mainOutputFile = match.getMainOutputFile();
return isMainApkCompatibleWithDevice(mainOutputFile, variantAbiFilters, deviceAbis)
? ImmutableList.of(mainOutputFile)
: ImmutableList.<OutputFile>of();
}
private static boolean isMainApkCompatibleWithDevice(
OutputFile mainOutputFile,
Collection<String> variantAbiFilters,
Collection<String> deviceAbis) {
// so far, we are not dealing with the pure split files...
if (getFilter(mainOutputFile, OutputFile.ABI) == null && variantAbiFilters != null) {
// if we have a match that has no abi filter, and we have variant-level filters, then
// we need to make sure that the variant filters are compatible with the device abis.
for (String abi : deviceAbis) {
if (variantAbiFilters.contains(abi)) {
return true;
}
}
return false;
}
return true;
}
@Nullable
private static String getFilter(@NonNull OutputFile outputFile, @NonNull String filterType) {
for (FilterData filterData : outputFile.getFilters()) {
if (filterData.getFilterType().equals(filterType)) {
return filterData.getIdentifier();
}
}
return null;
}
}