| /* |
| * 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; |
| } |
| } |