From f17ce94a4c10555e7ba87bbf4d4f2af35b70cc61 Mon Sep 17 00:00:00 2001 From: sopo Date: Tue, 23 May 2023 15:57:29 +0100 Subject: [PATCH] Prepare for release 1.15.1. --- README.md | 2 +- gradle.properties | 2 +- .../bundletool/commands/BuildApksCommand.java | 8 +- .../commands/BuildSdkApksForAppCommand.java | 93 +++++- .../commands/BuildSdkBundleCommand.java | 14 + .../build/bundletool/model/AppBundle.java | 1 + .../build/bundletool/model/BundleModule.java | 5 +- .../bundletool/model/ManifestEditor.java | 34 +++ .../tools/build/bundletool/model/SdkAsar.java | 18 +- .../build/bundletool/model/SdkBundle.java | 1 + .../bundletool/model/utils/BundleParser.java | 2 + .../model/version/BundleToolVersion.java | 2 +- .../DeclarativeWatchFaceBundleValidator.java | 17 +- .../BuildSdkApksForAppCommandTest.java | 288 ++++++++++++++++-- .../commands/BuildSdkBundleCommandTest.java | 51 ++++ .../bundletool/model/ManifestEditorTest.java | 35 +++ ...clarativeWatchFaceBundleValidatorTest.java | 38 +++ 17 files changed, 555 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index dd00987e..04d5322b 100644 --- a/README.md +++ b/README.md @@ -46,4 +46,4 @@ https://developer.android.com/studio/command-line/bundletool ## Releases -Latest release: [1.15.0](https://github.com/google/bundletool/releases) +Latest release: [1.15.1](https://github.com/google/bundletool/releases) diff --git a/gradle.properties b/gradle.properties index 37aa9e8e..2dfcd6c8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -release_version = 1.15.0 +release_version = 1.15.1 diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java index 8ecd74e0..61af5968 100644 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java @@ -1008,13 +1008,7 @@ private ImmutableMap getValidatedSdkModules( getValidatedSdkBundlesByPackageName(closer, tempDir); validateSdkBundlesMatchAppBundleDependencies(appBundle, sdkBundles); return sdkBundles.entrySet().stream() - .collect( - toImmutableMap( - Entry::getKey, - entry -> - entry.getValue().getModule().toBuilder() - .setSdkModulesConfig(entry.getValue().getSdkModulesConfig()) - .build())); + .collect(toImmutableMap(Entry::getKey, entry -> entry.getValue().getModule())); } private ImmutableMap getValidatedSdkAsarsByPackageName( diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommand.java index a29aeebb..66ba7e4a 100644 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommand.java @@ -21,6 +21,7 @@ import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable; import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileHasExtension; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import com.android.bundle.RuntimeEnabledSdkConfigProto.SdkSplitPropertiesInheritedFromApp; import com.android.tools.build.bundletool.androidtools.Aapt2Command; @@ -32,6 +33,7 @@ import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.Password; import com.android.tools.build.bundletool.model.SdkAsar; +import com.android.tools.build.bundletool.model.SdkBundle; import com.android.tools.build.bundletool.model.SignerConfig; import com.android.tools.build.bundletool.model.SigningConfiguration; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; @@ -41,6 +43,7 @@ import com.android.tools.build.bundletool.model.utils.files.BufferedIo; import com.android.tools.build.bundletool.sdkmodule.SdkModuleToAppBundleModuleConverter; import com.android.tools.build.bundletool.validation.SdkAsarValidator; +import com.android.tools.build.bundletool.validation.SdkBundleValidator; import com.google.auto.value.AutoValue; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -64,7 +67,10 @@ public abstract class BuildSdkApksForAppCommand { public static final String COMMAND_NAME = "build-sdk-apks-for-app"; + private static final Flag SDK_BUNDLE_LOCATION_FLAG = Flag.path("sdk-bundle"); + private static final Flag SDK_ARCHIVE_LOCATION_FLAG = Flag.path("sdk-archive"); + private static final Flag INHERITED_APP_PROPERTIES_LOCATION_FLAG = Flag.path("app-properties"); @@ -81,7 +87,9 @@ public abstract class BuildSdkApksForAppCommand { private static final SystemEnvironmentProvider DEFAULT_PROVIDER = new DefaultSystemEnvironmentProvider(); - public abstract Path getSdkArchivePath(); + public abstract Optional getSdkBundlePath(); + + public abstract Optional getSdkArchivePath(); public abstract SdkSplitPropertiesInheritedFromApp getInheritedAppProperties(); @@ -107,6 +115,9 @@ public static BuildSdkApksForAppCommand.Builder builder() { @AutoValue.Builder public abstract static class Builder { + /** Sets the path to the SDK bundle file. Must have the extension ".asb". */ + public abstract Builder setSdkBundlePath(Path sdkBundlePath); + /** Sets the path to the SDK archive file. Must have the extension ".asar". */ public abstract Builder setSdkArchivePath(Path sdkArchivePath); @@ -160,7 +171,11 @@ public BuildSdkApksForAppCommand build() { setExecutorServiceInternal(createInternalExecutorService(DEFAULT_THREAD_POOL_SIZE)); setExecutorServiceCreatedByBundleTool(true); } - return autoBuild(); + BuildSdkApksForAppCommand command = autoBuild(); + checkState( + command.getSdkBundlePath().isPresent() ^ command.getSdkArchivePath().isPresent(), + "One and only one of SdkBundlePath and SdkArchivePath should be set."); + return command; } } @@ -168,11 +183,21 @@ public static CommandHelp help() { return CommandHelp.builder() .setCommandName(COMMAND_NAME) .setCommandDescription(CommandDescription.builder().setShortDescription("").build()) + .addFlag( + FlagDescription.builder() + .setFlagName(SDK_BUNDLE_LOCATION_FLAG.getName()) + .setExampleValue("sdk.asb") + .setDescription( + "Path to SDK bundle to generate app-specific split APKs from. Can" + + " not be used together with the `sdk-archive` flag.") + .build()) .addFlag( FlagDescription.builder() .setFlagName(SDK_ARCHIVE_LOCATION_FLAG.getName()) .setExampleValue("sdk.asar") - .setDescription("Path to SDK archive to generate app-specific split APKs from.") + .setDescription( + "Path to SDK archive to generate app-specific split APKs from. Can" + + " not be used together with the `sdk-bundle` flag.") .build()) .addFlag( FlagDescription.builder() @@ -250,11 +275,11 @@ static BuildSdkApksForAppCommand fromFlags( ParsedFlags flags, PrintStream out, SystemEnvironmentProvider systemEnvironmentProvider) { BuildSdkApksForAppCommand.Builder command = BuildSdkApksForAppCommand.builder() - .setSdkArchivePath(SDK_ARCHIVE_LOCATION_FLAG.getRequiredValue(flags)) .setInheritedAppProperties( INHERITED_APP_PROPERTIES_LOCATION_FLAG.getRequiredValue(flags)) .setOutputFile(OUTPUT_FILE_FLAG.getRequiredValue(flags)); - + SDK_BUNDLE_LOCATION_FLAG.getValue(flags).ifPresent(command::setSdkBundlePath); + SDK_ARCHIVE_LOCATION_FLAG.getValue(flags).ifPresent(command::setSdkArchivePath); AAPT2_PATH_FLAG .getValue(flags) .ifPresent( @@ -267,14 +292,58 @@ static BuildSdkApksForAppCommand fromFlags( public void execute() { validateInput(); + if (getSdkBundlePath().isPresent()) { + executeForSdkBundle(); + } else if (getSdkArchivePath().isPresent()) { + executeForSdkArchive(); + } else { + throw new IllegalStateException("whaaat"); + } + } + + private void validateInput() { + if (getSdkBundlePath().isPresent()) { + checkFileExistsAndReadable(getSdkBundlePath().get()); + checkFileHasExtension("ASB file", getSdkBundlePath().get(), ".asb"); + } + if (getSdkArchivePath().isPresent()) { + checkFileExistsAndReadable(getSdkArchivePath().get()); + checkFileHasExtension("ASAR file", getSdkArchivePath().get(), ".asar"); + } + } + + private void executeForSdkBundle() { + try (TempDirectory tempDir = new TempDirectory(getClass().getSimpleName()); + ZipFile sdkBundleZip = new ZipFile(getSdkBundlePath().get().toFile())) { + Path modulesPath = tempDir.getPath().resolve(EXTRACTED_SDK_MODULES_FILE_NAME); + try (ZipFile modulesZip = getModulesZip(sdkBundleZip, modulesPath)) { + SdkBundleValidator sdkBundleValidator = SdkBundleValidator.create(); + sdkBundleValidator.validateModulesFile(modulesZip); + // SdkBundle#getVersionCode is not used in `build-apks`. It does not matter what + // value we set here, so we are just setting 0. + SdkBundle sdkBundle = + SdkBundle.buildFromZip(sdkBundleZip, modulesZip, /* versionCode= */ 0); + sdkBundleValidator.validate(sdkBundle); + generateAppApks(sdkBundle.getModule(), tempDir); + } + } catch (ZipException e) { + throw CommandExecutionException.builder() + .withInternalMessage("ASB is not a valid zip file.") + .withCause(e) + .build(); + } catch (IOException e) { + throw new UncheckedIOException("An error occurred when processing the SDK bundle.", e); + } + } + private void executeForSdkArchive() { try (TempDirectory tempDir = new TempDirectory(getClass().getSimpleName()); - ZipFile asarZip = new ZipFile(getSdkArchivePath().toFile())) { + ZipFile asarZip = new ZipFile(getSdkArchivePath().get().toFile())) { Path modulesPath = tempDir.getPath().resolve(EXTRACTED_SDK_MODULES_FILE_NAME); try (ZipFile modulesZip = getModulesZip(asarZip, modulesPath)) { SdkAsarValidator.validateModulesFile(modulesZip); SdkAsar sdkAsar = SdkAsar.buildFromZip(asarZip, modulesZip, modulesPath); - generateAppApks(sdkAsar, tempDir); + generateAppApks(sdkAsar.getModule(), tempDir); } } catch (ZipException e) { throw CommandExecutionException.builder() @@ -286,15 +355,9 @@ public void execute() { } } - private void validateInput() { - checkFileExistsAndReadable(getSdkArchivePath()); - checkFileHasExtension("ASAR file", getSdkArchivePath(), ".asar"); - } - - private void generateAppApks(SdkAsar sdkAsar, TempDirectory tempDirectory) { + private void generateAppApks(BundleModule sdkModule, TempDirectory tempDirectory) { BundleModule convertedAppModule = - new SdkModuleToAppBundleModuleConverter(sdkAsar.getModule(), getInheritedAppProperties()) - .convert(); + new SdkModuleToAppBundleModuleConverter(sdkModule, getInheritedAppProperties()).convert(); DaggerBuildSdkApksForAppManagerComponent.builder() .setBuildSdkApksForAppCommand(this) .setModule(convertedAppModule) diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommand.java index ddde296d..86320e86 100644 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommand.java @@ -34,8 +34,10 @@ import com.android.tools.build.bundletool.flags.Flag; import com.android.tools.build.bundletool.flags.ParsedFlags; import com.android.tools.build.bundletool.io.ZipFlingerBundleSerializer; +import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.ManifestEditor; import com.android.tools.build.bundletool.model.SdkBundle; import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; @@ -224,6 +226,7 @@ public void execute() { .map( moduleZipPath -> BundleModuleParser.parseSdkBundleModule(moduleZipPath, sdkModulesConfig)) + .map(BuildSdkBundleCommand::sanitizeManifest) .collect(toImmutableList()); new SdkBundleModulesValidator().validateSdkBundleModules(modules); @@ -324,6 +327,17 @@ private static void populateConfigFromJson( } } + private static BundleModule sanitizeManifest(BundleModule bundleModule) { + return bundleModule.toBuilder() + .setAndroidManifest(sanitizeManifest(bundleModule.getAndroidManifest())) + .build(); + } + + private static AndroidManifest sanitizeManifest(AndroidManifest androidManifest) { + return androidManifest.applyMutators( + ImmutableList.of(ManifestEditor::removePermissions, ManifestEditor::removeComponents)); + } + public static CommandHelp help() { return CommandHelp.builder() .setCommandName(COMMAND_NAME) diff --git a/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java b/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java index 2d4707f7..f0592825 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java +++ b/src/main/java/com/android/tools/build/bundletool/model/AppBundle.java @@ -89,6 +89,7 @@ public static AppBundle buildFromZip(ZipFile bundleFile) { bundleConfig.getType(), Version.of(bundleConfig.getBundletool().getVersion()), apexConfig, + /* sdkModulesConfig= */ Optional.empty(), NON_MODULE_DIRECTORIES)), bundleConfig, readBundleMetadata(bundleFile)); diff --git a/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java b/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java index 2b3d59a8..f0c10439 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java +++ b/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java @@ -153,7 +153,10 @@ public AndroidManifest getAndroidManifest() { public abstract Optional getRuntimeEnabledSdkConfig(); - /** Only present for modules of type SDK_DEPENDENCY_MODULE. */ + /** + * Only present for modules of type SDK_DEPENDENCY_MODULE in app bundles, as well as in SDK + * bundles and ASARs. + */ public abstract Optional getSdkModulesConfig(); /** diff --git a/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java b/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java index 1026afba..af56ea37 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ManifestEditor.java @@ -50,8 +50,10 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.NO_NAMESPACE_URI; +import static com.android.tools.build.bundletool.model.AndroidManifest.PERMISSION_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.PROPERTY_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.PROVIDER_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.RECEIVER_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REMOVABLE_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REQUIRED_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REQUIRED_BY_PRIVACY_SANDBOX_SDK_ATTRIBUTE_NAME; @@ -741,6 +743,38 @@ private boolean hasRequiredByPrivacySandboxSdkAttr(XmlProtoElementBuilder elemen .isPresent(); } + /** Removes custom permissions from the manifest. */ + @CanIgnoreReturnValue + public ManifestEditor removePermissions() { + manifestElement.removeChildrenElementsIf( + childElement -> + childElement.isElement() + && childElement.getElement().getName().equals(PERMISSION_ELEMENT_NAME)); + return this; + } + + /** Removes any components from the application element. */ + @CanIgnoreReturnValue + public ManifestEditor removeComponents() { + manifestElement + .getOptionalChildElement(APPLICATION_ELEMENT_NAME) + .ifPresent(this::removeComponents); + return this; + } + + private void removeComponents(XmlProtoElementBuilder element) { + ImmutableSet componentNames = + ImmutableSet.of( + ACTIVITY_ELEMENT_NAME, + SERVICE_ELEMENT_NAME, + PROVIDER_ELEMENT_NAME, + RECEIVER_ELEMENT_NAME); + element.removeChildrenElementsIf( + childElement -> + childElement.isElement() + && componentNames.contains(childElement.getElement().getName())); + } + /** Generates the modified manifest. */ @CheckReturnValue public AndroidManifest save() { diff --git a/src/main/java/com/android/tools/build/bundletool/model/SdkAsar.java b/src/main/java/com/android/tools/build/bundletool/model/SdkAsar.java index 18070689..d8270195 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/SdkAsar.java +++ b/src/main/java/com/android/tools/build/bundletool/model/SdkAsar.java @@ -52,16 +52,14 @@ public static SdkAsar buildFromZip(ZipFile asar, ZipFile modulesFile, Path modul SdkModulesConfig sdkModulesConfig = readSdkModulesConfig(modulesFile); BundleModule sdkModule = Iterables.getOnlyElement( - sanitize( - extractModules( - modulesFile, - BundleType.REGULAR, - Version.of(sdkModulesConfig.getBundletool().getVersion()), - /* apexConfig= */ Optional.empty(), - /* nonModuleDirectories= */ ImmutableSet.of()))) - .toBuilder() - .setSdkModulesConfig(sdkModulesConfig) - .build(); + sanitize( + extractModules( + modulesFile, + BundleType.REGULAR, + Version.of(sdkModulesConfig.getBundletool().getVersion()), + /* apexConfig= */ Optional.empty(), + Optional.of(sdkModulesConfig), + /* nonModuleDirectories= */ ImmutableSet.of()))); SdkAsar.Builder sdkAsarBuilder = builder() .setModule(sdkModule) diff --git a/src/main/java/com/android/tools/build/bundletool/model/SdkBundle.java b/src/main/java/com/android/tools/build/bundletool/model/SdkBundle.java index 851affb7..4497e906 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/SdkBundle.java +++ b/src/main/java/com/android/tools/build/bundletool/model/SdkBundle.java @@ -59,6 +59,7 @@ public static SdkBundle buildFromZip( BundleType.REGULAR, Version.of(sdkModulesConfig.getBundletool().getVersion()), /* apexConfig= */ Optional.empty(), + Optional.of(sdkModulesConfig), /* nonModuleDirectories= */ ImmutableSet.of())) .get(0)) .setSdkModulesConfig(sdkModulesConfig) diff --git a/src/main/java/com/android/tools/build/bundletool/model/utils/BundleParser.java b/src/main/java/com/android/tools/build/bundletool/model/utils/BundleParser.java index 7d535536..b533a97d 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/utils/BundleParser.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/BundleParser.java @@ -112,6 +112,7 @@ public static ImmutableList extractModules( BundleType bundleType, Version bundletoolVersion, Optional apexConfig, + Optional sdkModulesConfig, ImmutableSet nonModuleDirectories) { Map moduleBuilders = new HashMap<>(); Enumeration entries = bundleFile.entries(); @@ -136,6 +137,7 @@ public static ImmutableList extractModules( .setBundleType(bundleType) .setBundletoolVersion(bundletoolVersion); apexConfig.ifPresent(bundleModuleBuilder::setBundleApexConfig); + sdkModulesConfig.ifPresent(bundleModuleBuilder::setSdkModulesConfig); return bundleModuleBuilder; }); diff --git a/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java index 016d254a..bb325388 100644 --- a/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java +++ b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java @@ -26,7 +26,7 @@ */ public final class BundleToolVersion { - private static final String CURRENT_VERSION = "1.15.0"; + private static final String CURRENT_VERSION = "1.15.1"; /** Returns the version of BundleTool being run. */ public static Version getCurrentVersion() { diff --git a/src/main/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidator.java index a8766f18..f9b194f0 100644 --- a/src/main/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidator.java @@ -64,6 +64,9 @@ private void validateDwfBundle(AppBundle bundle) { validateBaseContainsLayoutDefinitions(baseModule); validateBaseHasNoExecutableComponents(baseModule); + validateBaseHasNoLibs(baseModule); + validateBaseHasNoRoot(baseModule); + Optional> optionalRuntime = getOptionalRuntime(bundle); optionalRuntime.ifPresent(this::validateRuntimeIsConditionallyInstalled); @@ -161,7 +164,7 @@ private void validateMinSdkIsCorrect(BundleModule baseModule, boolean hasRuntime private void validateNoUnexpectedDexFiles(BundleModule baseModule, boolean hasRuntime) { ImmutableList dexFiles = - baseModule.findEntriesUnderPath(ZipPath.create("/dex/")).collect(toImmutableList()); + baseModule.findEntriesUnderPath(BundleModule.DEX_DIRECTORY).collect(toImmutableList()); if (!hasRuntime) { assertWithUserMessage( dexFiles.isEmpty(), "Watch face with minSdk >= 33 cannot have dex files."); @@ -174,6 +177,18 @@ private void validateNoUnexpectedDexFiles(BundleModule baseModule, boolean hasRu } } + private void validateBaseHasNoLibs(BundleModule baseModule) { + boolean hasLibs = + baseModule.findEntriesUnderPath(BundleModule.LIB_DIRECTORY).findFirst().isPresent(); + assertWithUserMessage(!hasLibs, "Watch face cannot have any external libraries."); + } + + private void validateBaseHasNoRoot(BundleModule baseModule) { + boolean hasRoot = + baseModule.findEntriesUnderPath(BundleModule.ROOT_DIRECTORY).findFirst().isPresent(); + assertWithUserMessage(!hasRoot, "Watch face cannot have any files in the root of the package."); + } + private void assertWithUserMessage(boolean condition, String message) { if (!condition) { throw InvalidBundleException.builder().withUserMessage(message).build(); diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommandTest.java index 211f2407..d13a8d4d 100644 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkApksForAppCommandTest.java @@ -27,6 +27,7 @@ import static com.android.tools.build.bundletool.testing.TestUtils.createZipBuilderForModules; import static com.android.tools.build.bundletool.testing.TestUtils.createZipBuilderForModulesWithoutManifest; import static com.android.tools.build.bundletool.testing.TestUtils.createZipBuilderForSdkAsarWithModules; +import static com.android.tools.build.bundletool.testing.TestUtils.createZipBuilderForSdkBundleWithModules; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredBuilderPropertyException; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredFlagException; import static com.android.tools.build.bundletool.testing.TestUtils.extractAndroidManifest; @@ -100,6 +101,7 @@ public class BuildSdkApksForAppCommandTest { private Path tmpDir; private Path sdkAsarPath; + private Path sdkBundlePath; private Path appBundlePath; private Path extractedModulesFilePath; private Path inheritedAppPropertiesConfigPath; @@ -125,6 +127,7 @@ public static void setUpClass() throws Exception { public void setUp() throws Exception { tmpDir = tmp.getRoot().toPath(); sdkAsarPath = tmpDir.resolve("sdk.asar"); + sdkBundlePath = tmpDir.resolve("sdk.asb"); appBundlePath = tmpDir.resolve("app.aab"); inheritedAppPropertiesConfigPath = tmpDir.resolve("config.json"); Files.writeString( @@ -141,7 +144,7 @@ public void setUp() throws Exception { } @Test - public void buildingViaFlagsAndBuilderHasSameResult_defaults() { + public void buildingViaFlagsAndBuilderHasSameResult_defaults_withSdkArchive() { ByteArrayOutputStream output = new ByteArrayOutputStream(); BuildSdkApksForAppCommand commandViaFlags = BuildSdkApksForAppCommand.fromFlags( @@ -168,6 +171,34 @@ public void buildingViaFlagsAndBuilderHasSameResult_defaults() { assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } + @Test + public void buildingViaFlagsAndBuilderHasSameResult_defaults_withSdkBundle() { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + BuildSdkApksForAppCommand commandViaFlags = + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-bundle=" + sdkBundlePath, + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath, + "--aapt2=" + AAPT2_PATH), + new PrintStream(output), + systemEnvironmentProvider); + + BuildSdkApksForAppCommand.Builder commandViaBuilder = + BuildSdkApksForAppCommand.builder() + .setSdkBundlePath(sdkBundlePath) + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .setAapt2Command(commandViaFlags.getAapt2Command().get()) + .setExecutorService(commandViaFlags.getExecutorService()) + .setExecutorServiceCreatedByBundleTool(true); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); + + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); + } + @Test public void buildingViaFlagsAndBuilderHasSameResult_optionalSigning() { ByteArrayOutputStream output = new ByteArrayOutputStream(); @@ -203,23 +234,63 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalSigning() { } @Test - public void sdkAsarNotSet_throws() { - expectMissingRequiredBuilderPropertyException( - "sdkArchivePath", - () -> - BuildSdkApksForAppCommand.builder() - .setInheritedAppProperties(INHERITED_APP_PROPERTIES) - .setOutputFile(outputFilePath) - .build()); + public void sdkAsarNotSet_sdkBundleNotSet_throws() { + Throwable exceptionFromBuilder = + assertThrows( + IllegalStateException.class, + () -> + BuildSdkApksForAppCommand.builder() + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .build()); + assertThat(exceptionFromBuilder) + .hasMessageThat() + .contains("One and only one of SdkBundlePath and SdkArchivePath should be set."); + + Throwable exceptionFromFlags = + assertThrows( + IllegalStateException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath))); + assertThat(exceptionFromFlags) + .hasMessageThat() + .contains("One and only one of SdkBundlePath and SdkArchivePath should be set."); + } - expectMissingRequiredFlagException( - "sdk-archive", - () -> - BuildSdkApksForAppCommand.fromFlags( - new FlagParser() - .parse( - "--app-properties=" + inheritedAppPropertiesConfigPath, - "--output=" + outputFilePath))); + @Test + public void sdkAsarSet_sdkBundleSet_throws() { + Throwable exceptionFromBuilder = + assertThrows( + IllegalStateException.class, + () -> + BuildSdkApksForAppCommand.builder() + .setSdkBundlePath(sdkBundlePath) + .setSdkArchivePath(sdkAsarPath) + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .build()); + assertThat(exceptionFromBuilder) + .hasMessageThat() + .contains("One and only one of SdkBundlePath and SdkArchivePath should be set."); + + Throwable exceptionFromFlags = + assertThrows( + IllegalStateException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-bundle=" + sdkBundlePath, + "--sdk-archive=" + sdkAsarPath, + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath))); + assertThat(exceptionFromFlags) + .hasMessageThat() + .contains("One and only one of SdkBundlePath and SdkArchivePath should be set."); } @Test @@ -260,6 +331,84 @@ public void outputFileNotSet_throws() { "--app-properties=" + inheritedAppPropertiesConfigPath))); } + @Test + public void sdkArchiveSet_fileDoesNotExist_throws() { + Throwable exceptionFromFlags = + assertThrows( + IllegalArgumentException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-archive=non-existent-file.asar", + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath)) + .execute()); + assertThat(exceptionFromFlags) + .hasMessageThat() + .contains("File 'non-existent-file.asar' was not found."); + } + + @Test + public void sdkArchiveSet_badExtension_throws() throws Exception { + Path filePathWithBadExtension = tmpDir.resolve("sdk.sdk"); + ZipBuilder sdkBundleZipBuilder = + createZipBuilderForSdkBundleWithModules( + createZipBuilderForModules(), extractedModulesFilePath); + sdkBundleZipBuilder.writeTo(filePathWithBadExtension); + Throwable exceptionFromFlags = + assertThrows( + IllegalArgumentException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-archive=" + filePathWithBadExtension, + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath)) + .execute()); + assertThat(exceptionFromFlags).hasMessageThat().contains("expected to have '.asar' extension."); + } + + @Test + public void sdkBundleSet_fileDoesNotExist_throws() { + Throwable exceptionFromFlags = + assertThrows( + IllegalArgumentException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-bundle=non-existent-file.asb", + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath)) + .execute()); + assertThat(exceptionFromFlags) + .hasMessageThat() + .contains("File 'non-existent-file.asb' was not found."); + } + + @Test + public void sdkBundleSet_badExtension_throws() throws Exception { + Path filePathWithBadExtension = tmpDir.resolve("sdk.sdk"); + ZipBuilder sdkBundleZipBuilder = + createZipBuilderForSdkBundleWithModules( + createZipBuilderForModules(), extractedModulesFilePath); + sdkBundleZipBuilder.writeTo(filePathWithBadExtension); + Throwable exceptionFromFlags = + assertThrows( + IllegalArgumentException.class, + () -> + BuildSdkApksForAppCommand.fromFlags( + new FlagParser() + .parse( + "--sdk-bundle=" + filePathWithBadExtension, + "--app-properties=" + inheritedAppPropertiesConfigPath, + "--output=" + outputFilePath)) + .execute()); + assertThat(exceptionFromFlags).hasMessageThat().contains("expected to have '.asb' extension."); + } + @Test public void modulesZipMissingManifestInAsar_validationFails() throws Exception { createZipBuilderForSdkAsarWithModules( @@ -279,7 +428,25 @@ public void modulesZipMissingManifestInAsar_validationFails() throws Exception { } @Test - public void generatesModuleSplit() throws Exception { + public void modulesZipMissingManifestInSdkBundle_validationFails() throws Exception { + createZipBuilderForSdkBundleWithModules( + createZipBuilderForModulesWithoutManifest(), extractedModulesFilePath) + .writeTo(sdkBundlePath); + BuildSdkApksForAppCommand command = + BuildSdkApksForAppCommand.builder() + .setSdkBundlePath(sdkBundlePath) + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .build(); + + Exception e = assertThrows(InvalidBundleException.class, command::execute); + assertThat(e) + .hasMessageThat() + .contains("Module 'base' is missing mandatory file 'manifest/AndroidManifest.xml'."); + } + + @Test + public void generatesModuleSplit_withSdkArchive() throws Exception { ZipBuilder asarZipBuilder = createZipBuilderForSdkAsarWithModules( createZipBuilderForModules(), extractedModulesFilePath); @@ -308,7 +475,36 @@ public void generatesModuleSplit() throws Exception { } @Test - public void generateModuleSplit_sameAsBuildApks() throws Exception { + public void generatesModuleSplit_withSdkBundle() throws Exception { + ZipBuilder sdkBundleZipBuilder = + createZipBuilderForSdkBundleWithModules( + createZipBuilderForModules(), extractedModulesFilePath); + sdkBundleZipBuilder.writeTo(sdkBundlePath); + BuildSdkApksForAppCommand command = + BuildSdkApksForAppCommand.builder() + .setSdkBundlePath(sdkBundlePath) + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .build(); + + command.execute(); + + ZipFile apkSetFile = new ZipFile(outputFilePath.toFile()); + assertThat(apkSetFile.size()).isEqualTo(1); + String apkPathInsideArchive = + "splits/" + SdkBundleBuilder.PACKAGE_NAME.replace(".", "") + "-master.apk"; + assertThat(ZipUtils.allFileEntriesPaths(apkSetFile)) + .containsExactly(ZipPath.create(apkPathInsideArchive)); + File apkFile = ApkSetUtils.extractFromApkSetFile(apkSetFile, apkPathInsideArchive, tmpDir); + AndroidManifest apkManifest = extractAndroidManifest(apkFile, tmpDir); + assertThat(apkManifest.getPackageName()).isEqualTo(INHERITED_APP_PROPERTIES.getPackageName()); + assertThat(apkManifest.getVersionCode()).hasValue(INHERITED_APP_PROPERTIES.getVersionCode()); + assertThat(apkManifest.getMinSdkVersion()) + .hasValue(INHERITED_APP_PROPERTIES.getMinSdkVersion()); + } + + @Test + public void generateModuleSplit_withAsar_sameAsBuildApks() throws Exception { String validCertDigest = "96:C7:EC:89:3E:69:2A:25:BA:4D:EE:C1:84:E8:33:3F:34:7D:6D:12:26:A1:C1:AA:70:A2:8A:DB:75:3E:02:0A"; ZipBuilder asarZipBuilder = @@ -367,6 +563,60 @@ public void generateModuleSplit_sameAsBuildApks() throws Exception { assertThat(getFileHash(buildSdkApksForAppOutputApk)).isEqualTo(getFileHash(buildApksOutputApk)); } + @Test + public void generateModuleSplit_withSdkBundle_sameAsBuildApks() throws Exception { + String validCertDigest = + "96:C7:EC:89:3E:69:2A:25:BA:4D:EE:C1:84:E8:33:3F:34:7D:6D:12:26:A1:C1:AA:70:A2:8A:DB:75:3E:02:0A"; + ZipBuilder sdkBundleZipBuilder = + createZipBuilderForSdkBundleWithModules( + createZipBuilderForModules(), extractedModulesFilePath); + sdkBundleZipBuilder.writeTo(sdkBundlePath); + BuildSdkApksForAppCommand buildSdkApksForAppCommand = + BuildSdkApksForAppCommand.builder() + .setSdkBundlePath(sdkBundlePath) + .setInheritedAppProperties(INHERITED_APP_PROPERTIES) + .setOutputFile(outputFilePath) + .build(); + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + new BundleModuleBuilder("base") + .setManifest( + androidManifest("com.test.app", withMinSdkVersion(ANDROID_L_API_VERSION))) + .setRuntimeEnabledSdkConfig( + RuntimeEnabledSdkConfig.newBuilder() + .addRuntimeEnabledSdk( + RuntimeEnabledSdk.newBuilder() + .setPackageName(SdkBundleBuilder.PACKAGE_NAME) + .setVersionMajor(1) + .setVersionMinor(1) + .setCertificateDigest(validCertDigest) + .setResourcesPackageId(2)) + .build()) + .build()) + .build(); + new AppBundleSerializer().writeToDisk(appBundle, appBundlePath); + BuildApksCommand buildApksCommand = + BuildApksCommand.builder() + .setBundlePath(appBundlePath) + .setOutputFile(buildApksOutputFilePath) + .setRuntimeEnabledSdkBundlePaths(ImmutableSet.of(sdkBundlePath)) + .build(); + + buildSdkApksForAppCommand.execute(); + buildApksCommand.execute(); + + String sdkSplitPath = + "splits/" + SdkBundleBuilder.PACKAGE_NAME.replace(".", "") + "-master.apk"; + ZipFile buildApksOutputSet = new ZipFile(buildApksOutputFilePath.toFile()); + File buildApksOutputApk = + ApkSetUtils.extractFromApkSetFile(buildApksOutputSet, sdkSplitPath, tmpDir); + ZipFile buildSdkApksForApOutputSet = new ZipFile(outputFilePath.toFile()); + File buildSdkApksForAppOutputApk = + ApkSetUtils.extractFromApkSetFile(buildSdkApksForApOutputSet, sdkSplitPath, tmpDir); + assertThat(getFileHash(buildSdkApksForAppOutputApk)).isEqualTo(getFileHash(buildApksOutputApk)); + } + @Test public void printHelpDoesNotCrash() { BuildSdkApksForAppCommand.help(); diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommandTest.java index a37723ba..f30e0d0d 100644 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildSdkBundleCommandTest.java @@ -18,9 +18,17 @@ import static com.android.bundle.Targeting.Abi.AbiAlias.X86; import static com.android.bundle.Targeting.Abi.AbiAlias.X86_64; +import static com.android.tools.build.bundletool.model.AndroidManifest.ACTIVITY_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.APPLICATION_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.PERMISSION_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.PROVIDER_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.RECEIVER_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.SERVICE_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.RuntimeEnabledSdkVersionEncoder.VERSION_MAJOR_MAX_VALUE; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withSplitId; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.xmlElement; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.xmlNode; import static com.android.tools.build.bundletool.testing.TargetingUtils.assetsDirectoryTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.languageTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.expectMissingRequiredBuilderPropertyException; @@ -870,6 +878,49 @@ public void sdkBundleConfig_isSaved() throws Exception { } } + @Test + public void androidManifestSanitized() throws Exception { + XmlNode manifest = + xmlNode( + xmlElement( + "manifest", + xmlNode(xmlElement(PERMISSION_ELEMENT_NAME)), + xmlNode( + xmlElement( + APPLICATION_ELEMENT_NAME, + xmlNode(xmlElement(ACTIVITY_ELEMENT_NAME)), + xmlNode(xmlElement(SERVICE_ELEMENT_NAME)), + xmlNode(xmlElement(PROVIDER_ELEMENT_NAME)), + xmlNode(xmlElement(RECEIVER_ELEMENT_NAME)))))); + Path module = + new ZipBuilder() + .addFileWithProtoContent(ZipPath.create("manifest/AndroidManifest.xml"), manifest) + .addFileWithContent(ZipPath.create("dex/classes.dex"), "dex".getBytes(UTF_8)) + .writeTo(tmpDir.resolve("base.zip")); + Path sdkInterfaceDescriptorsPath = buildSdkInterfaceDescriptors("sdk-api.jar"); + + BuildSdkBundleCommand.builder() + .setOutputPath(bundlePath) + .setModulesPaths(ImmutableList.of(module)) + .setSdkModulesConfig(sdkModulesConfigPath) + .setSdkInterfaceDescriptors(sdkInterfaceDescriptorsPath) + .build() + .execute(); + + XmlNode sanitizedManifest = + xmlNode(xmlElement("manifest", xmlNode(xmlElement(APPLICATION_ELEMENT_NAME)))); + try (ZipFile bundle = new ZipFile(bundlePath.toFile())) { + ZipEntry modulesEntry = bundle.getEntry("modules.resm"); + Path modulesPath = tmpDir.resolve("modules.resm"); + Files.write(modulesPath, ZipUtils.asByteSource(bundle, modulesEntry).read()); + try (ZipFile modules = new ZipFile(modulesPath.toFile())) { + TruthZip.assertThat(modules) + .hasFile("base/manifest/AndroidManifest.xml") + .withContent(sanitizedManifest.toByteArray()); + } + } + } + @Test public void printHelp_doesNotCrash() { BuildSdkBundleCommand.help(); diff --git a/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java b/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java index d1453d8b..183951f7 100644 --- a/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ManifestEditorTest.java @@ -40,6 +40,7 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.PERMISSION_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.PROVIDER_ELEMENT_NAME; +import static com.android.tools.build.bundletool.model.AndroidManifest.RECEIVER_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REMOVABLE_ELEMENT_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REQUIRED_ATTRIBUTE_NAME; import static com.android.tools.build.bundletool.model.AndroidManifest.REQUIRED_BY_PRIVACY_SANDBOX_SDK_ATTRIBUTE_NAME; @@ -1392,6 +1393,40 @@ public void removeRequiredByPrivacySandboxSdkAttributes() { xmlNode(xmlElement(REMOVABLE_ELEMENT_NAME)))))))))); } + @Test + public void removePermissions() { + AndroidManifest originalManifest = + AndroidManifest.create( + xmlNode(xmlElement("manifest", xmlNode(xmlElement(PERMISSION_ELEMENT_NAME))))); + + AndroidManifest editedManifest = originalManifest.toEditor().removePermissions().save(); + + assertThat(editedManifest).isEqualTo(AndroidManifest.create(xmlNode(xmlElement("manifest")))); + } + + @Test + public void removeComponents() { + AndroidManifest originalManifest = + AndroidManifest.create( + xmlNode( + xmlElement( + "manifest", + xmlNode( + xmlElement( + APPLICATION_ELEMENT_NAME, + xmlNode(xmlElement(ACTIVITY_ELEMENT_NAME)), + xmlNode(xmlElement(SERVICE_ELEMENT_NAME)), + xmlNode(xmlElement(PROVIDER_ELEMENT_NAME)), + xmlNode(xmlElement(RECEIVER_ELEMENT_NAME))))))); + + AndroidManifest editedManifest = originalManifest.toEditor().removeComponents().save(); + + assertThat(editedManifest) + .isEqualTo( + AndroidManifest.create( + xmlNode(xmlElement("manifest", xmlNode(xmlElement(APPLICATION_ELEMENT_NAME)))))); + } + @Test public void addManifestChildElement() { AndroidManifest androidManifest = AndroidManifest.create(xmlNode(xmlElement("manifest"))); diff --git a/src/test/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidatorTest.java index 8668053d..90765b4f 100644 --- a/src/test/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/DeclarativeWatchFaceBundleValidatorTest.java @@ -271,6 +271,44 @@ public void invalidSimpleDwf_hasDexInBase() { assertThat(e).hasMessageThat().contains("cannot have dex files"); } + @Test + public void invalidSimpleDwf_hasLibsInBase() { + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + new BundleModuleBuilder("base") + .setManifest( + createDwfManifest( + withUsesFeatureElement( + AndroidManifest.USES_FEATURE_HARDWARE_WATCH_NAME))) + .addFile("/res/raw/watchface.xml") + .addFile("/lib/sample.so") + .build()) + .build(); + + InvalidBundleException e = assertThrowsForBundle(appBundle); + assertThat(e).hasMessageThat().contains("cannot have any external libraries"); + } + + @Test + public void invalidSimpleDwf_hasRootFilesInBase() { + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + new BundleModuleBuilder("base") + .setManifest( + createDwfManifest( + withUsesFeatureElement( + AndroidManifest.USES_FEATURE_HARDWARE_WATCH_NAME))) + .addFile("/res/raw/watchface.xml") + .addFile("/root/some-file.txt") + .build()) + .build(); + + InvalidBundleException e = assertThrowsForBundle(appBundle); + assertThat(e).hasMessageThat().contains("cannot have any files in the root of the package"); + } + @Test public void invalidDwfWithEmbeddedRuntime_unexpectedDexFile() { AppBundle appBundle =