提交 9e2c2173 编写于 作者: T Tom Dobek

Prepare for release 0.4.2.

上级 91b79cf4
文件模式从 100644 更改为 100755
......@@ -24,5 +24,5 @@ Read more about the App Bundle format and Bundletool's usage at
## Releases
Latest release: [0.4.1](https://github.com/google/bundletool/releases)
Latest release: [0.4.2](https://github.com/google/bundletool/releases)
......@@ -32,7 +32,7 @@ configurations {
// The repackaging rules are defined in the "shadowJar" task below.
dependencies {
compile "com.android.tools:r8:1.0.10"
compile "com.android.tools:r8:1.0.25"
compile "com.android.tools.build:apkzlib:3.2.0-alpha06"
compile "com.android.tools.ddms:ddmlib:26.2.0-alpha04"
......
release_version = 0.4.1
release_version = 0.4.2
......@@ -69,7 +69,9 @@ public class BundleToolMain {
BuildBundleCommand.fromFlags(flags).execute();
break;
case BuildApksCommand.COMMAND_NAME:
BuildApksCommand.fromFlags(flags).execute();
try (AdbServer adbServer = DdmlibAdbServer.getInstance()) {
BuildApksCommand.fromFlags(flags, adbServer).execute();
}
break;
case ExtractApksCommand.COMMAND_NAME:
ExtractApksCommand.fromFlags(flags).execute();
......
......@@ -17,6 +17,7 @@
package com.android.tools.build.bundletool.commands;
import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileDoesNotExist;
import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndExecutable;
import static com.android.tools.build.bundletool.utils.files.FilePreconditions.checkFileExistsAndReadable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
......@@ -26,12 +27,15 @@ import com.android.bundle.Commands.Variant;
import com.android.bundle.Config.BundleConfig;
import com.android.bundle.Config.Bundletool;
import com.android.bundle.Config.Compression;
import com.android.bundle.Devices.DeviceSpec;
import com.android.bundle.Targeting.ApkTargeting;
import com.android.bundle.Targeting.SdkVersion;
import com.android.bundle.Targeting.SdkVersionTargeting;
import com.android.bundle.Targeting.VariantTargeting;
import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription;
import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription;
import com.android.tools.build.bundletool.device.AdbServer;
import com.android.tools.build.bundletool.device.DeviceAnalyzer;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.exceptions.ValidationException;
import com.android.tools.build.bundletool.io.ApkSerializerManager;
......@@ -52,7 +56,9 @@ import com.android.tools.build.bundletool.optimizations.OptimizationsMerger;
import com.android.tools.build.bundletool.splitters.BundleSharder;
import com.android.tools.build.bundletool.splitters.ModuleSplitter;
import com.android.tools.build.bundletool.targeting.AlternativeVariantTargetingPopulator;
import com.android.tools.build.bundletool.utils.EnvironmentVariableProvider;
import com.android.tools.build.bundletool.utils.SdkToolsLocator;
import com.android.tools.build.bundletool.utils.SystemEnvironmentVariableProvider;
import com.android.tools.build.bundletool.utils.Versions;
import com.android.tools.build.bundletool.utils.flags.Flag;
import com.android.tools.build.bundletool.utils.flags.Flag.Password;
......@@ -69,6 +75,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executors;
......@@ -91,6 +98,11 @@ public abstract class BuildApksCommand {
private static final Flag<Boolean> GENERATE_UNIVERSAL_APK_FLAG = Flag.booleanFlag("universal");
private static final Flag<Integer> MAX_THREADS_FLAG = Flag.positiveInteger("max-threads");
private static final Flag<Path> ADB_PATH_FLAG = Flag.path("adb");
private static final Flag<Boolean> CONNECTED_DEVICE_FLAG = Flag.booleanFlag("connected-device");
private static final Flag<String> DEVICE_ID_FLAG = Flag.string("device-id");
private static final String ANDROID_HOME_VARIABLE = "ANDROID_HOME";
// Signing-related flags: should match flags from apksig library.
private static final Flag<Path> KEYSTORE_FLAG = Flag.path("ks");
private static final Flag<String> KEY_ALIAS_FLAG = Flag.string("ks-key-alias");
......@@ -99,12 +111,25 @@ public abstract class BuildApksCommand {
private static final String APK_SET_ARCHIVE_EXTENSION = "apks";
private static final EnvironmentVariableProvider DEFAULT_PROVIDER =
new SystemEnvironmentVariableProvider();
public abstract Path getBundlePath();
public abstract Path getOutputFile();
public abstract ImmutableSet<OptimizationDimension> getOptimizationDimensions();
public abstract boolean getGenerateOnlyForConnectedDevice();
public abstract Optional<String> getDeviceId();
/** Required when getGenerateOnlyForConnectedDevice is true. */
abstract Optional<AdbServer> getAdbServer();
/** Required when getGenerateOnlyForConnectedDevice is true. */
public abstract Optional<Path> getAdbPath();
public abstract boolean getGenerateOnlyUniversalApk();
public abstract Optional<Aapt2Command> getAapt2Command();
......@@ -117,6 +142,7 @@ public abstract class BuildApksCommand {
public static Builder builder() {
return new AutoValue_BuildApksCommand.Builder()
.setGenerateOnlyUniversalApk(false)
.setGenerateOnlyForConnectedDevice(false)
.setOptimizationDimensions(ImmutableSet.of())
.setExecutorService(createInternalExecutorService(DEFAULT_THREAD_POOL_SIZE));
}
......@@ -142,6 +168,23 @@ public abstract class BuildApksCommand {
*/
public abstract Builder setGenerateOnlyUniversalApk(boolean universalOnly);
/**
* Sets if the generated APK Set will contain APKs compatible only with the connected device.
*/
public abstract Builder setGenerateOnlyForConnectedDevice(boolean onlyForConnectedDevice);
/**
* Sets the device serial number. Required if more than one device including emulators is
* connected.
*/
public abstract Builder setDeviceId(String deviceId);
/** Path to the ADB binary. Required if ANDROID_HOME environment variable is not set. */
public abstract Builder setAdbPath(Path adbPath);
/** The caller is responsible for the lifecycle of the {@link AdbServer}. */
public abstract Builder setAdbServer(AdbServer adbServer);
/** Provides a wrapper around the execution of the aapt2 command. */
public abstract Builder setAapt2Command(Aapt2Command aapt2Command);
......@@ -170,6 +213,12 @@ public abstract class BuildApksCommand {
"Cannot generate universal APK and specify optimization dimensions at the same time.");
}
if (command.getGenerateOnlyForConnectedDevice() && command.getGenerateOnlyUniversalApk()) {
throw new ValidationException(
"Cannot generate universal APK and optimize for the connected device "
+ "at the same time.");
}
if (!APK_SET_ARCHIVE_EXTENSION.equals(MoreFiles.getFileExtension(command.getOutputFile()))) {
throw ValidationException.builder()
.withMessage(
......@@ -182,11 +231,15 @@ public abstract class BuildApksCommand {
}
}
public static BuildApksCommand fromFlags(ParsedFlags flags) {
return fromFlags(flags, System.out);
public static BuildApksCommand fromFlags(ParsedFlags flags, AdbServer adbServer) {
return fromFlags(flags, System.out, DEFAULT_PROVIDER, adbServer);
}
static BuildApksCommand fromFlags(ParsedFlags flags, PrintStream out) {
static BuildApksCommand fromFlags(
ParsedFlags flags,
PrintStream out,
EnvironmentVariableProvider environmentVariableProvider,
AdbServer adbServer) {
BuildApksCommand.Builder buildApksCommand =
BuildApksCommand.builder()
.setBundlePath(BUNDLE_LOCATION_FLAG.getRequiredValue(flags))
......@@ -228,6 +281,31 @@ public abstract class BuildApksCommand {
+ "keystore via the flag --ks. See the command help for more information.");
}
boolean connectedDeviceMode = CONNECTED_DEVICE_FLAG.getValue(flags).orElse(false);
CONNECTED_DEVICE_FLAG
.getValue(flags)
.ifPresent(buildApksCommand::setGenerateOnlyForConnectedDevice);
DEVICE_ID_FLAG.getValue(flags).ifPresent(buildApksCommand::setDeviceId);
// Applied only when --connected-device flag is set, because we don't want to fail command
// if ADB cannot be found in a normal mode.
if (connectedDeviceMode) {
Path adbPath =
ADB_PATH_FLAG
.getValue(flags)
.orElseGet(
() ->
environmentVariableProvider
.getVariable(ANDROID_HOME_VARIABLE)
.flatMap(path -> new SdkToolsLocator().locateAdb(Paths.get(path)))
.orElseThrow(
() ->
new CommandExecutionException(
"Unable to determine the location of ADB. Please set the "
+ "--adb flag or define ANDROID_HOME environment "
+ "variable.")));
buildApksCommand.setAdbPath(adbPath).setAdbServer(adbServer);
}
flags.checkNoUnknownFlags();
return buildApksCommand.build();
......@@ -242,6 +320,12 @@ public abstract class BuildApksCommand {
Aapt2Command aapt2Command = getAapt2Command().orElseGet(() -> extractAapt2FromJar(tempDir));
// Fail fast with ADB before generating any APKs.
Optional<DeviceSpec> targetedDevice = Optional.empty();
if (getGenerateOnlyForConnectedDevice()) {
targetedDevice = Optional.of(getDeviceSpec());
}
try (ZipFile bundleZip = new ZipFile(getBundlePath().toFile())) {
AppBundleValidator bundleValidator = new AppBundleValidator();
......@@ -271,6 +355,23 @@ public abstract class BuildApksCommand {
boolean generateSplitApks = !getGenerateOnlyUniversalApk() && !targetsOnlyPreL(appBundle);
boolean generateStandaloneApks = getGenerateOnlyUniversalApk() || targetsPreL(appBundle);
if (targetedDevice.isPresent()) {
if (targetedDevice.get().getSdkVersion() >= Versions.ANDROID_L_API_VERSION) {
generateStandaloneApks = false;
if (!generateSplitApks) {
throw new CommandExecutionException(
"App Bundle targets pre-L devices, but the device has SDK version higher "
+ "or equal to L.");
}
} else {
generateSplitApks = false;
if (!generateStandaloneApks) {
throw new CommandExecutionException(
"App Bundle targets L+ devices, but the device has SDK version lower than L.");
}
}
}
if (generateSplitApks) {
splitApks = generateSplitApks(allModules, apkOptimizations, bundleVersion);
}
......@@ -294,11 +395,18 @@ public abstract class BuildApksCommand {
splitApks, standaloneApks);
// Create variants and serialize APKs.
ImmutableList<Variant> allVariantsWithTargeting =
new ApkSerializerManager(getExecutorService())
.serializeAndGenerateAllVariants(
allApks, apkSetBuilder, appBundle, getGenerateOnlyUniversalApk());
ApkSerializerManager apkSerializerManager = new ApkSerializerManager(getExecutorService());
ImmutableList<Variant> allVariantsWithTargeting = ImmutableList.of();
if (targetedDevice.isPresent()) {
allVariantsWithTargeting =
ImmutableList.of(
apkSerializerManager.serializeAndGenerateVariantForDevice(
targetedDevice.get(), allApks, apkSetBuilder, appBundle));
} else {
allVariantsWithTargeting =
apkSerializerManager.serializeAndGenerateAllVariants(
allApks, apkSetBuilder, appBundle, getGenerateOnlyUniversalApk());
}
// Finalize the output archive.
apkSetBuilder.setTableOfContentsFile(
BuildApksResult.newBuilder()
......@@ -319,6 +427,13 @@ public abstract class BuildApksCommand {
return getOutputFile();
}
private DeviceSpec getDeviceSpec() {
AdbServer adbServer = getAdbServer().get();
adbServer.init(getAdbPath().get());
return new DeviceAnalyzer(adbServer).getDeviceSpec(getDeviceId());
}
private ApkSetBuilder createApkSetBuilder(
Aapt2Command aapt2Command,
Optional<SigningConfiguration> signingConfiguration,
......@@ -347,6 +462,16 @@ public abstract class BuildApksCommand {
private void validateInput() {
checkFileExistsAndReadable(getBundlePath());
checkFileDoesNotExist(getOutputFile());
if (getGenerateOnlyForConnectedDevice()) {
checkArgument(
getAdbServer().isPresent(),
"Property 'adbServer' is required when 'generateOnlyForConnectedDevice' is true.");
checkArgument(
getAdbPath().isPresent(),
"Property 'adbPath' is required when 'generateOnlyForConnectedDevice' is true.");
checkFileExistsAndExecutable(getAdbPath().get());
}
}
private ImmutableList<ModuleSplit> generateSplitApks(
......@@ -438,8 +563,10 @@ public abstract class BuildApksCommand {
.setCommandDescription(
CommandDescription.builder()
.setShortDescription(
"Generates an APK Set archive containing all possible split APKs and "
+ "standalone APKs.")
"Generates an APK Set archive containing either all possible split APKs and "
+ "standalone APKs or APKs optimized for the connected device "
+ "(see %s flag).",
CONNECTED_DEVICE_FLAG)
.build())
.addFlag(
FlagDescription.builder()
......@@ -534,6 +661,35 @@ public abstract class BuildApksCommand {
+ "flag is not set, the keystore password will be tried. If that fails, "
+ "the password will be requested on the prompt.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(CONNECTED_DEVICE_FLAG.getName())
.setOptional(true)
.setDescription(
"If set, will generate APK Set optimized for the connected device. The "
+ "generated APK Set will only be installable on that specific class of "
+ "devices.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(ADB_PATH_FLAG.getName())
.setExampleValue("path/to/adb")
.setOptional(true)
.setDescription(
"Path to the adb utility. If absent, an attempt will be made to locate it if "
+ "the %s environment variable is set. Used only if %s flag is set.",
ANDROID_HOME_VARIABLE, CONNECTED_DEVICE_FLAG)
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(DEVICE_ID_FLAG.getName())
.setExampleValue("device-serial-name")
.setOptional(true)
.setDescription(
"Device serial name. Required when more than one device or emulator is "
+ "connected. Used only if %s flag is set.",
CONNECTED_DEVICE_FLAG)
.build())
.build();
}
......
......@@ -151,6 +151,12 @@ public abstract class CommandHelp {
/** Sets the short description of the command. */
abstract Builder setShortDescription(String shortDescription);
/** Same as {@link #setShortDescription(String)} but allowing formatted string. */
@FormatMethod
Builder setShortDescription(String shortDescriptionFormat, Object... args) {
return setShortDescription(String.format(shortDescriptionFormat, args));
}
abstract ImmutableList.Builder<String> additionalParagraphsBuilder();
/** Adds an additional paragraph of the command description. */
......
......@@ -17,6 +17,7 @@
package com.android.tools.build.bundletool.device;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.android.bundle.Devices.DeviceSpec;
......@@ -25,11 +26,14 @@ import com.android.bundle.Targeting.Abi.AbiAlias;
import com.android.bundle.Targeting.AbiTargeting;
import com.android.bundle.Targeting.ApkTargeting;
import com.android.bundle.Targeting.VariantTargeting;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.exceptions.ValidationException;
import com.android.tools.build.bundletool.model.AbiName;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.util.stream.Stream;
/** A {@link TargetingDimensionMatcher} that provides matching on ABI architecture. */
public final class AbiMatcher extends TargetingDimensionMatcher<AbiTargeting> {
......@@ -75,9 +79,24 @@ public final class AbiMatcher extends TargetingDimensionMatcher<AbiTargeting> {
return false;
}
}
// Nothing matched. We successfully match only if we are supposed to match
// "everything else than in alternatives" when our values set is empty.
return valuesList.isEmpty();
// At this point we know that any device's abiAlias is not within values or alternatives.
// The only viable scenario is when the split has no values and the semantic is to match
// "all but alternatives". Otherwise, neither value or alternative can satisfy the device spec
// therefore this module can't be supported by the device.
if (!valuesList.isEmpty()) {
ImmutableList<String> supportedAbis =
Stream.concat(valuesList.stream(), alternativesList.stream())
.map(AbiName::fromProto)
.map(AbiName::getPlatformName)
.collect(toImmutableList());
throw CommandExecutionException.builder()
.withMessage(
"The app doesn't support ABI architectures of the device. "
+ "Device ABIs: %s, app ABIs: %s.",
getDeviceSpec().getSupportedAbisList(), supportedAbis)
.build();
}
return true;
}
@Override
......
......@@ -33,23 +33,23 @@ public abstract class AdbServer implements Closeable {
public abstract void init(Path pathToAdb);
public ImmutableList<Device> getDevices() throws TimeoutException {
waitTillConnected(ADB_TIMEOUT_MS);
waitTillInitialDeviceListPopulated(ADB_TIMEOUT_MS);
return getDevicesInternal();
}
protected abstract ImmutableList<Device> getDevicesInternal();
public abstract boolean isConnected();
public abstract boolean hasInitialDeviceList();
private final void waitTillConnected(long timeoutMs) throws TimeoutException {
if (isConnected()) {
private final void waitTillInitialDeviceListPopulated(long timeoutMs) throws TimeoutException {
if (hasInitialDeviceList()) {
return;
}
Stopwatch stopwatch = Stopwatch.createStarted();
try {
// We typically need to wait a little for the ADB connection.
sleep(50);
while (!isConnected()) {
while (!hasInitialDeviceList()) {
if (stopwatch.elapsed().toMillis() > timeoutMs) {
throw new TimeoutException(
String.format("Timed out (%d ms) while waiting for ADB.", timeoutMs));
......
......@@ -27,6 +27,7 @@ import com.android.bundle.Devices.DeviceSpec;
import com.android.bundle.Targeting.ApkTargeting;
import com.android.bundle.Targeting.VariantTargeting;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.model.ModuleSplit;
import com.android.tools.build.bundletool.model.ZipPath;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
......@@ -95,7 +96,7 @@ public class ApkMatcher {
ApkTargeting apkTargeting = apkDescription.getTargeting();
boolean isSplitApk = apkDescription.hasSplitApkMetadata();
if (matchesApk(apkTargeting, isSplitApk, moduleName)) {
if (matchesApkTargeting(apkTargeting, isSplitApk, moduleName)) {
matchedApksBuilder.add(ZipPath.create(apkDescription.getPath()));
}
}
......@@ -109,7 +110,8 @@ public class ApkMatcher {
*
* @return whether to deliver the APK to the device
*/
public boolean matchesApk(ApkTargeting apkTargeting, boolean isSplit, String moduleName) {
public boolean matchesApkTargeting(
ApkTargeting apkTargeting, boolean isSplit, String moduleName) {
boolean matchesTargeting = matchesApkTargeting(apkTargeting);
if (isSplit) {
......@@ -126,14 +128,6 @@ public class ApkMatcher {
}
}
private boolean matchesVariantTargeting(VariantTargeting variantTargeting) {
return sdkVersionMatcher
.getVariantTargetingPredicate()
.and(abiMatcher.getVariantTargetingPredicate())
.and(screenDensityMatcher.getVariantTargetingPredicate())
.test(variantTargeting);
}
private boolean matchesApkTargeting(ApkTargeting apkTargeting) {
return sdkVersionMatcher
.getApkTargetingPredicate()
......@@ -142,4 +136,20 @@ public class ApkMatcher {
.and(languageMatcher.getApkTargetingPredicate())
.test(apkTargeting);
}
public boolean matchesModuleSplit(ModuleSplit moduleSplit) {
return matchesVariantTargeting(moduleSplit.getVariantTargeting())
&& matchesApkTargeting(
moduleSplit.getApkTargeting(),
/* isSplit= */ !moduleSplit.isStandalone(),
moduleSplit.getModuleName().getName());
}
private boolean matchesVariantTargeting(VariantTargeting variantTargeting) {
return sdkVersionMatcher
.getVariantTargetingPredicate()
.and(abiMatcher.getVariantTargetingPredicate())
.and(screenDensityMatcher.getVariantTargetingPredicate())
.test(variantTargeting);
}
}
......@@ -92,9 +92,9 @@ public class DdmlibAdbServer extends AdbServer {
}
@Override
public synchronized boolean isConnected() {
public synchronized boolean hasInitialDeviceList() {
checkState(state == State.INITIALIZED, "Android Debug Bridge is not initialized.");
return adb.isConnected();
return adb.hasInitialDeviceList();
}
@Override
......
......@@ -16,6 +16,8 @@
package com.android.tools.build.bundletool.device;
import static com.google.common.base.Preconditions.checkState;
import com.android.bundle.Devices.DeviceSpec;
import com.android.ddmlib.IDevice.DeviceState;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
......@@ -49,11 +51,19 @@ public class DeviceAnalyzer {
try {
Device device = getAndValidateDevice(deviceId);
// device.getVersion().getApiLevel() returns 1 in case of failure.
int deviceSdkVersion = device.getVersion().getApiLevel();
checkState(deviceSdkVersion > 1, "Error retrieving device SDK version. Please try again.");
int deviceDensity = device.getDensity();
checkState(deviceDensity > 0, "Error retrieving device density. Please try again.");
ImmutableList<String> supportedAbis = device.getAbis();
checkState(!supportedAbis.isEmpty(), "Error retrieving device ABIs. Please try again.");
return DeviceSpec.newBuilder()
.setSdkVersion(device.getVersion().getApiLevel())
.addAllSupportedAbis(device.getAbis())
.setSdkVersion(deviceSdkVersion)
.addAllSupportedAbis(supportedAbis)
.addSupportedLocales(getMainLocale(device))
.setScreenDensity(device.getDensity())
.setScreenDensity(deviceDensity)
.build();
} catch (TimeoutException e) {
throw CommandExecutionException.builder()
......
......@@ -23,12 +23,17 @@ import com.android.bundle.Targeting.ApkTargeting;
import com.android.bundle.Targeting.SdkVersion;
import com.android.bundle.Targeting.SdkVersionTargeting;
import com.android.bundle.Targeting.VariantTargeting;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import java.util.stream.Stream;
/** A {@link TargetingDimensionMatcher} that provides matching on SDK level. */
/** A {@link TargetingDimensionMatcher} that provides matching on SDK version. */
public final class SdkVersionMatcher extends TargetingDimensionMatcher<SdkVersionTargeting> {
private final int deviceSdkVersion;
public SdkVersionMatcher(DeviceSpec deviceSpec) {
super(deviceSpec);
deviceSdkVersion = deviceSpec.getSdkVersion();
}
@Override
......@@ -40,16 +45,29 @@ public final class SdkVersionMatcher extends TargetingDimensionMatcher<SdkVersio
SdkVersion sdkValue =
targeting.getValueCount() == 0 ? SdkVersion.getDefaultInstance() : targeting.getValue(0);
if (!matchesDeviceSdk(sdkValue, getDeviceSpec().getSdkVersion())) {
// First check if any value or alternative matches the device spec. If not, this device is
// incompatible with the app.
boolean anyMatch =
Stream.concat(Stream.of(sdkValue), targeting.getAlternativesList().stream())
.anyMatch(sdkVal -> matchesDeviceSdk(sdkVal, deviceSdkVersion));
if (!anyMatch) {
throw CommandExecutionException.builder()
.withMessage(
"The app doesn't support SDK version of the device: (%d).",
getDeviceSpec().getSdkVersion())
.build();
}
if (!matchesDeviceSdk(sdkValue, deviceSdkVersion)) {
return false;
}
// Check if there is a better match among alternatives.
for (SdkVersion alternativeSdkValue : targeting.getAlternativesList()) {
if (isBetterSdkMatch(
/* candidate= */ alternativeSdkValue,
/* contestedValue= */ sdkValue,
getDeviceSpec().getSdkVersion())) {
/* candidate= */ alternativeSdkValue, /* contestedValue= */ sdkValue, deviceSdkVersion)) {
return false;
}
}
......
......@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;