Merge Android Pie into master

Bug: 112104996
Change-Id: Ie05d3082adfc859729dfd395e80c597fe4e5e496
diff --git a/bundletool/Android.mk b/bundletool/Android.mk
new file mode 100644
index 0000000..b6c20f8
--- /dev/null
+++ b/bundletool/Android.mk
@@ -0,0 +1,37 @@
+LOCAL_PATH:= $(call my-dir)
+
+## bundletool script
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundletool
+LOCAL_MODULE_TAG := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/bundletool$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/bundletool | $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+## tool jar
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundletool
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := guavalib jsr305lib dagger2-auto-value-host error_prone_annotations-2.0.18
+
+LOCAL_ANNOTATION_PROCESSORS := dagger2-auto-value-host
+LOCAL_ANNOTATION_PROCESSOR_CLASSES := com.google.auto.value.processor.AutoValueProcessor
+
+LOCAL_JAR_MANIFEST := manifest.txt
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/bundletool/etc/bundletool b/bundletool/etc/bundletool
new file mode 100644
index 0000000..ac7cac3
--- /dev/null
+++ b/bundletool/etc/bundletool
@@ -0,0 +1,89 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=bundletool.jar
+libdir="$progdir"
+
+if [ ! -r "$libdir/$jarfile" ]; then
+    # set bundletool.jar location for the SDK case
+    libdir="$libdir/lib"
+fi
+
+
+if [ ! -r "$libdir/$jarfile" ]; then
+    # set bundletool.jar location for the Android tree case
+    libdir=`dirname "$progdir"`/framework
+fi
+
+if [ ! -r "$libdir/$jarfile" ]; then
+    echo `basename "$prog"`": can't find $jarfile"
+    exit 1
+fi
+
+# By default, give bundletool a max heap size of 1 gig. This can be overridden
+# by using a "-J" option (see below).
+defaultMx="-Xmx1024M"
+
+# The following will extract any initial parameters of the form
+# "-J<stuff>" from the command line and pass them to the Java
+# invocation (instead of to bundletool). This makes it possible for you to add
+# a command-line parameter such as "-JXmx256M" in your scripts, for
+# example. "java" (with no args) and "java -X" give a summary of
+# available options.
+
+javaOpts=""
+
+while expr "x$1" : 'x-J' >/dev/null; do
+    opt=`expr "x$1" : 'x-J\(.*\)'`
+    javaOpts="${javaOpts} -${opt}"
+    if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then
+        defaultMx="no"
+    fi
+    shift
+done
+
+if [ "${defaultMx}" != "no" ]; then
+    javaOpts="${javaOpts} ${defaultMx}"
+fi
+
+if [ "$OSTYPE" = "cygwin" ]; then
+    # For Cygwin, convert the jarfile path into native Windows style.
+    jarpath=`cygpath -w "$libdir/$jarfile"`
+else
+    jarpath="$libdir/$jarfile"
+fi
+
+exec java $javaOpts -jar "$jarpath" "$@"
diff --git a/bundletool/etc/bundletool.bat b/bundletool/etc/bundletool.bat
new file mode 100644
index 0000000..09a9d36
--- /dev/null
+++ b/bundletool/etc/bundletool.bat
@@ -0,0 +1,88 @@
+@echo off↵
+REM Copyright (C) 2017 The Android Open Source Project↵
+REM↵
+REM Licensed under the Apache License, Version 2.0 (the "License");↵
+REM you may not use this file except in compliance with the License.↵
+REM You may obtain a copy of the License at↵
+REM↵
+REM     http://www.apache.org/licenses/LICENSE-2.0↵
+REM↵
+REM Unless required by applicable law or agreed to in writing, software↵
+REM distributed under the License is distributed on an "AS IS" BASIS,↵
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.↵
+REM See the License for the specific language governing permissions and↵
+REM limitations under the License.↵
+↵
+REM don't modify the caller's environment↵
+setlocal↵
+↵
+REM Locate bundletool.jar in the directory where bundletool.bat was found and start it.↵
+↵
+REM Set up prog to be the path of this script, including following symlinks,↵
+REM and set up progdir to be the fully-qualified pathname of its directory.↵
+set prog=%~f0↵
+↵
+rem Check we have a valid Java.exe in the path.↵
+set java_exe=↵
+if exist    "%~dp0..\tools\lib\find_java.bat" call    "%~dp0..\tools\lib\find_java.bat"↵
+if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat"↵
+if not defined java_exe goto :EOF↵
+↵
+set jarfile=bundletool.jar↵
+set "frameworkdir=%~dp0"↵
+rem frameworkdir must not end with a dir sep.↵
+set "frameworkdir=%frameworkdir:~0,-1%"↵
+↵
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk↵
+    set "frameworkdir=%~dp0lib"↵
+↵
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk↵
+    set "frameworkdir=%~dp0..\framework"↵
+↵
+:JarFileOk↵
+↵
+set "jarpath=%frameworkdir%\%jarfile%"↵
+↵
+set javaOpts=↵
+set args=↵
+↵
+REM By default, give bundletool a max heap size of 1 gig and a stack size of 1meg.↵
+rem This can be overridden by using "-JXmx..." and "-JXss..." options below.↵
+set defaultXmx=-Xmx1024M↵
+set defaultXss=-Xss1m↵
+↵
+REM Capture all arguments that are not -J options.↵
+REM Note that when reading the input arguments with %1, the cmd.exe↵
+REM automagically converts --name=value arguments into 2 arguments "--name"↵
+REM followed by "value". Dx has been changed to know how to deal with that.↵
+set params=↵
+↵
+:firstArg↵
+if [%1]==[] goto endArgs↵
+set a=%~1↵
+↵
+    if [%defaultXmx%]==[] goto notXmx↵
+    if %a:~0,5% NEQ -JXmx goto notXmx↵
+        set defaultXmx=↵
+    :notXmx↵
+↵
+    if [%defaultXss%]==[] goto notXss↵
+    if %a:~0,5% NEQ -JXss goto notXss↵
+        set defaultXss=↵
+    :notXss↵
+↵
+    if %a:~0,2% NEQ -J goto notJ↵
+        set javaOpts=%javaOpts% -%a:~2%↵
+        shift /1↵
+        goto firstArg↵
+↵
+    :notJ↵
+    set params=%params% %1↵
+    shift /1↵
+    goto firstArg↵
+↵
+:endArgs↵
+↵
+set javaOpts=%javaOpts% %defaultXmx% %defaultXss%↵
+call "%java_exe%" %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params%↵
+↵
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java
new file mode 100644
index 0000000..cbdf4e1
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/** Represents an app bundle. */
+public class AppBundle {
+
+  private ZipFile bundleFile;
+  private Map<String, BundleModule> modules;
+
+  public AppBundle(ZipFile bundleFile) {
+    this.bundleFile = bundleFile;
+    this.modules = new HashMap<>();
+    open();
+  }
+
+  private void open() {
+    Map<String, BundleModule.Builder> moduleBuilders = new HashMap<>();
+    Enumeration<? extends ZipEntry> entries = bundleFile.entries();
+    while (entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      Path path = Paths.get(entry.getName());
+      if (path.getNameCount() > 1) {
+        String moduleName = path.getName(0).toString();
+        BundleModule.Builder moduleBuilder =
+            moduleBuilders.computeIfAbsent(
+                moduleName, name -> new BundleModule.Builder(name, this));
+        moduleBuilder.addZipEntry(entry);
+      }
+    }
+    modules.putAll(Maps.transformValues(moduleBuilders, BundleModule.Builder::build));
+  }
+
+  public Map<String, BundleModule> getModules() {
+    return ImmutableMap.copyOf(modules);
+  }
+
+  public BundleModule getModule(String moduleName) {
+    return modules.get(moduleName);
+  }
+
+  public InputStream getEntryInputStream(ZipEntry entry) throws IOException {
+    return bundleFile.getInputStream(entry);
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java
new file mode 100644
index 0000000..8e30d9a
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import com.google.auto.value.AutoValue;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+/** Command responsible for building an App Bundle module. */
+@AutoValue
+public abstract class BuildModuleCommand {
+
+  public static final String COMMAND_NAME = "build-module";
+
+  private static final String OUTPUT_FLAG = "output";
+  private static final String MANIFEST_FLAG = "manifest";
+  private static final String MANIFEST_DIR_FLAG = "manifest-dir";
+  private static final String DEX_FLAG = "dex";
+  private static final String DEX_DIR_FLAG = "dex-dir";
+  private static final String RESOURCES_DIR_FLAG = "resources-dir";
+  private static final String ASSETS_DIR_FLAG = "assets-dir";
+  private static final String NATIVE_DIR_FLAG = "native-dir";
+
+  abstract Path getOutputPath();
+
+  abstract Optional<Path> getManifestPath();
+
+  abstract Optional<Path> getManifestDirPath();
+
+  abstract Optional<Path> getDexPath();
+
+  abstract Optional<Path> getDexDirPath();
+
+  abstract Optional<Path> getResourcesDirPath();
+
+  abstract Optional<Path> getAssetsDirPath();
+
+  abstract Optional<Path> getNativeDirPath();
+
+  public static Builder builder() {
+    return new AutoValue_BuildModuleCommand.Builder();
+  }
+
+  /** Builder for the {@link BuildModuleCommand} */
+  @AutoValue.Builder
+  public abstract static class Builder {
+    abstract Builder setOutputPath(Path outputPath);
+
+    abstract Builder setManifestPath(Path manifestPath);
+
+    abstract Builder setManifestDirPath(Path manifestDirPath);
+
+    abstract Builder setDexPath(Path dexPath);
+
+    abstract Builder setDexDirPath(Path dexDirPath);
+
+    abstract Builder setResourcesDirPath(Path resourcesDirPath);
+
+    abstract Builder setAssetsDirPath(Path assetsDirPath);
+
+    abstract Builder setNativeDirPath(Path nativeDirPath);
+
+    abstract BuildModuleCommand build();
+  }
+
+  static BuildModuleCommand fromFlags(FlagParser flagParser) {
+    Builder builder =
+        builder().setOutputPath(Paths.get(flagParser.getRequiredFlagValue(OUTPUT_FLAG)));
+    flagParser.getFlagValueAsPath(MANIFEST_FLAG).ifPresent(builder::setManifestPath);
+    flagParser.getFlagValueAsPath(MANIFEST_DIR_FLAG).ifPresent(builder::setManifestDirPath);
+    flagParser.getFlagValueAsPath(DEX_FLAG).ifPresent(builder::setDexPath);
+    flagParser.getFlagValueAsPath(DEX_DIR_FLAG).ifPresent(builder::setDexDirPath);
+    flagParser.getFlagValueAsPath(RESOURCES_DIR_FLAG).ifPresent(builder::setResourcesDirPath);
+    flagParser.getFlagValueAsPath(ASSETS_DIR_FLAG).ifPresent(builder::setAssetsDirPath);
+    flagParser.getFlagValueAsPath(NATIVE_DIR_FLAG).ifPresent(builder::setNativeDirPath);
+
+    return builder.build();
+  }
+
+  public void execute() {
+    validateInput();
+
+
+  }
+
+  private void validateInput() {
+    checkArgument(
+        getManifestPath().isPresent() || getManifestDirPath().isPresent(),
+        "One of --%s or --%s is required.",
+        MANIFEST_FLAG,
+        MANIFEST_DIR_FLAG);
+    checkArgument(
+        !getManifestPath().isPresent() || !getManifestDirPath().isPresent(),
+        "Cannot set both --%s and --%s flags.",
+        MANIFEST_FLAG,
+        MANIFEST_DIR_FLAG);
+    checkArgument(
+        !getDexPath().isPresent() || !getDexDirPath().isPresent(),
+        "Cannot set both --%s and --%s flags.",
+        DEX_FLAG,
+        DEX_DIR_FLAG);
+
+    checkArgument(!Files.exists(getOutputPath()), "File %s already exists.", getOutputPath());
+    checkFileExistsAndReadable(getManifestPath());
+    checkDirectoryExists(getManifestDirPath());
+    checkFileExistsAndReadable(getDexPath());
+    checkDirectoryExists(getDexDirPath());
+    checkDirectoryExists(getResourcesDirPath());
+    checkDirectoryExists(getAssetsDirPath());
+    checkDirectoryExists(getNativeDirPath());
+  }
+
+  private static void checkFileExistsAndReadable(Optional<Path> pathOptional) {
+    if (pathOptional.isPresent()) {
+      Path path = pathOptional.get();
+      checkArgument(Files.exists(path), "File '%s' was not found.", path);
+      checkArgument(Files.isReadable(path), "File '%s' is not readable.", path);
+    }
+  }
+
+  private static void checkDirectoryExists(Optional<Path> pathOptional) {
+    if (pathOptional.isPresent()) {
+      Path path = pathOptional.get();
+      checkArgument(Files.exists(path), "Directory '%s' was not found.", path);
+      checkArgument(Files.isDirectory(path), "'%s' is not a directory.");
+    }
+  }
+
+  public static void help() {
+    System.out.println(
+        String.format(
+            "bundletool %s --output=<path/to/module.zip> "
+                + "[--%s=<path/to/AndroidManifest.flat>|--%s=<path/to/manifest-dir/>] "
+                + "[--%s=<path/to/classes.dex>|--%s=<path/to/dex-dir/>] "
+                + "[--%s=<path/to/res/>] "
+                + "[--%s=<path/to/assets/>] "
+                + "[--%s=<path/to/lib/>] ",
+            COMMAND_NAME,
+            MANIFEST_FLAG,
+            MANIFEST_DIR_FLAG,
+            DEX_FLAG,
+            DEX_DIR_FLAG,
+            RESOURCES_DIR_FLAG,
+            ASSETS_DIR_FLAG,
+            NATIVE_DIR_FLAG));
+    System.out.println();
+    System.out.println(
+        "Builds a module as a zip from an app's project. Note that the resources and the "
+            + "AndroidManifest.xml must already have been compiled with aapt2.");
+    System.out.println();
+    System.out.println("--output: Path to the zip file to build.");
+    System.out.printf(
+        "--%s: Path to the AndroidManifest.flat compiled by aapt2. Use --%s if there "
+            + "are more than one.\n",
+        MANIFEST_FLAG, MANIFEST_DIR_FLAG);
+    System.out.printf(
+        "--%s: Path to the directory containing multiple Android manifests compiled by aapt2. "
+            + "A file named 'manifest-targeting.xml' must be present in the directory "
+            + "describing the targeting of each manifest present.\n",
+        MANIFEST_DIR_FLAG);
+    System.out.printf(
+        "--%s: Path to the dex file. Use --%s if there are more than one.\n",
+        DEX_FLAG, DEX_DIR_FLAG);
+    System.out.printf(
+        "--%s: Path to the directory containing multiple dex files. Unless all dex files must "
+            + "be included in the generated APKs (for MultiDex), a file named "
+            + "'dex-targeting.xml' must be present in the directory describing the targeting "
+            + "of the different dex files.\n",
+        DEX_DIR_FLAG);
+    System.out.printf(
+        "--%s: Path to the directory containing the resources file(s). A file named "
+            + "'resources.flat' must be present in that directory corresponding to the output "
+            + "of the aapt2 compilation of the resources.\n",
+        RESOURCES_DIR_FLAG);
+    System.out.printf("--%s: Path to the directory containing the assets.\n", ASSETS_DIR_FLAG);
+    System.out.printf(
+        "--%s: Path to the directory containing the native libraries.\n", NATIVE_DIR_FLAG);
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java
new file mode 100644
index 0000000..c88d287
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+/** Represents a single module inside App Bundle. */
+public class BundleModule {
+
+  private AppBundle parent;
+  private String name;
+  private List<ZipEntry> entries;
+
+  private BundleModule(String name, AppBundle parent, List<ZipEntry> entries) {
+    this.parent = parent;
+    this.name = name;
+    this.entries = entries;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public AppBundle getParent() {
+    return parent;
+  }
+
+  public List<ZipEntry> getEntries() {
+    return ImmutableList.copyOf(entries);
+  }
+
+  /** Builder for BundleModule. */
+  public static class Builder {
+    private List<ZipEntry> entries;
+    private String name;
+    private AppBundle parent;
+
+    public Builder(String name, AppBundle parent) {
+      this.name = name;
+      this.parent = parent;
+      this.entries = new ArrayList<>();
+    }
+
+    public Builder addZipEntry(ZipEntry entry) {
+      this.entries.add(entry);
+      return this;
+    }
+
+    public BundleModule build() {
+      return new BundleModule(name, parent, entries);
+    }
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java
new file mode 100644
index 0000000..330a991
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Main entry point of the bundle tool.
+ *
+ * <p>Consider running with -Dsun.zip.disableMemoryMapping when dealing with large bundles.
+ */
+public class BundleToolMain {
+
+  public static final String LINK_CMD = "link";
+  public static final String HELP_CMD = "help";
+
+  /** Parses the flags and routes to the appropriate command handler */
+  public static void main(String[] args) throws IOException {
+
+    FlagParser flagParser = new FlagParser();
+    try {
+      flagParser.parse(args);
+    } catch (FlagParser.ParseException e) {
+      System.out.println(String.format("Error while parsing the flags: %s", e.getMessage()));
+      return;
+    }
+    List<String> commands = flagParser.getCommands();
+
+    if (commands.isEmpty()) {
+      System.out.println("Error: you have to specify a command");
+      help();
+      return;
+    }
+
+    try {
+      switch (commands.get(0)) {
+        case BuildModuleCommand.COMMAND_NAME:
+          BuildModuleCommand.fromFlags(flagParser).execute();
+          break;
+        case SplitModuleCommand.COMMAND_NAME:
+          new SplitModuleCommand(flagParser).execute();
+          break;
+        case LINK_CMD:
+          throw new UnsupportedOperationException("Not implemented.");
+        case HELP_CMD:
+          if (commands.size() > 1) {
+            help(commands.get(1));
+          } else {
+            help();
+          }
+          return;
+        default:
+          System.out.println("Error: unrecognized command.");
+          help();
+      }
+    } catch (Exception e) {
+      System.out.println("Error: " + e.getMessage());
+    }
+  }
+
+  /** Displays a general help. */
+  public static void help() {
+    System.out.println(
+        String.format(
+            "bundletool [%s|%s|%s|%s] ...",
+            BuildModuleCommand.COMMAND_NAME, SplitModuleCommand.COMMAND_NAME, LINK_CMD, HELP_CMD));
+    System.out.println("Type: bundletool help [command] to learn more about a given command.");
+  }
+
+  /** Displays help about a given command. */
+  public static void help(String commandName) {
+    switch (commandName) {
+      case BuildModuleCommand.COMMAND_NAME:
+        BuildModuleCommand.help();
+        break;
+      case SplitModuleCommand.COMMAND_NAME:
+        SplitModuleCommand.help();
+        break;
+      case LINK_CMD:
+        System.out.println("Help is not yet available.");
+        break;
+      default:
+        System.out.println("Unrecognized command.");
+        help();
+        break;
+    }
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/Command.java b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java
new file mode 100644
index 0000000..670c0dc
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+/** Interface for command implementations. */
+public interface Command {
+
+  void execute() throws ExecutionException;
+
+  /** Error indicating something went wrong during executing the command. */
+  class ExecutionException extends RuntimeException {
+
+    public ExecutionException(String message) {
+      super(message);
+    }
+
+    public ExecutionException(String message, Throwable cause) {
+      super(message, cause);
+    }
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java
new file mode 100644
index 0000000..f6909af
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/** Implementation of the command to generate module splits. */
+public class SplitModuleCommand implements Command {
+
+  private final String bundleLocation;
+  private final String outputDirectory;
+  private final String moduleName;
+
+  public static final String COMMAND_NAME = "split-module";
+
+  private static final String BUNDLE_LOCATION_FLAG = "bundle";
+  private static final String OUTPUT_DIRECTORY_FLAG = "output";
+  private static final String MODULE_FLAG = "module";
+
+  public SplitModuleCommand(FlagParser parsedFlags) {
+    bundleLocation = parsedFlags.getRequiredFlagValue(BUNDLE_LOCATION_FLAG);
+    outputDirectory = parsedFlags.getRequiredFlagValue(OUTPUT_DIRECTORY_FLAG);
+    moduleName = parsedFlags.getRequiredFlagValue(MODULE_FLAG);
+  }
+
+  @Override
+  public void execute() throws ExecutionException {
+    try {
+      AppBundle appBundle = new AppBundle(new ZipFile(bundleLocation));
+      BundleModule module = appBundle.getModule(moduleName);
+      if (module == null) {
+        throw new ExecutionException(
+            String.format("Cannot find the %s module in the bundle", moduleName));
+      }
+      splitModule(moduleName, module, outputDirectory);
+    } catch (ZipException e) {
+      throw new ExecutionException("Zip error while opening the bundle " + e.getMessage(), e);
+    } catch (FileNotFoundException e) {
+      throw new ExecutionException("Bundle file not found", e);
+    } catch (IOException e) {
+      throw new ExecutionException("I/O error while processing the bundle " + e.getMessage(), e);
+    }
+  }
+
+  private void splitModule(String moduleName, BundleModule module, String outputDirectory) {
+    throw new UnsupportedOperationException("Not implemented");
+  }
+
+  public static void help() {
+    System.out.printf(
+        "bundletool %s --%s=[bundle.zip] --%s=[module-name] --%s=[output-dir]\n",
+        BUNDLE_LOCATION_FLAG, MODULE_FLAG, OUTPUT_DIRECTORY_FLAG, COMMAND_NAME);
+    System.out.println("Generates module splits for the given module of the bundle.");
+    System.out.println("For now, one split is generated containing all module's resources.");
+    System.out.println();
+    System.out.printf("--%s: the zip file containing an App Bundle.\n", BUNDLE_LOCATION_FLAG);
+    System.out.printf("--%s: module for which generate the splits.\n", MODULE_FLAG);
+    System.out.printf(
+        "--%s: the directory where the module zip files should be written to.\n",
+        OUTPUT_DIRECTORY_FLAG);
+  }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java
new file mode 100644
index 0000000..5f42165
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 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.tools.appbundle.bundletool.utils;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Utility for flag parsing, specific to the Bundle Tool.
+ *
+ * <p>The flags follow the below convention:
+ *
+ * <p>[bundle-tool] [command1] [command2] .. [command-n] [--flag1] [--flag2=v2].. [--flagn] where:
+ *
+ * <ul>
+ *   <li>commands: cannot start with "-".
+ *   <li>flags: have to start with "--". If they have "=" anything after the first occurrence is
+ *       considered a flag value. By default the flag value is an empty string.
+ * </ul>
+ */
+public class FlagParser {
+
+  private List<String> commands = new ArrayList<>();
+  private Map<String, String> flags = new HashMap<>();
+
+  /**
+   * Parses the given arguments populating the structures.
+   *
+   * <p>Calling this function removes any previous parsing results.
+   */
+  public FlagParser parse(String[] args) throws ParseException {
+    this.commands.clear();
+    // Need to wrap it into a proper list implementation to be able to remove elements.
+    List<String> argsToProcess = new ArrayList<>(Arrays.asList(args));
+    while (argsToProcess.size() > 0 && !argsToProcess.get(0).startsWith("-")) {
+      commands.add(argsToProcess.get(0));
+      argsToProcess.remove(0);
+    }
+    this.flags = parseFlags(argsToProcess);
+    return this;
+  }
+
+  private Map<String, String> parseFlags(List<String> args) throws ParseException {
+    Map<String, String> flagMap = new HashMap<>();
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        throw new ParseException(
+            String.format("Syntax error: flags should start with -- (%s)", arg));
+      }
+      String[] segments = arg.substring(2).split("=", 2);
+      String value = "";
+      if (segments.length == 2) {
+        value = segments[1];
+      }
+      if (flagMap.putIfAbsent(segments[0], value) != null) {
+        throw new ParseException(
+            String.format("Flag %s has been set more than once.", segments[0]));
+      }
+    }
+    return flagMap;
+  }
+
+  /** Returns true if a given flag has been set. */
+  public boolean isFlagSet(String flagName) {
+    return flags.containsKey(flagName);
+  }
+
+  /** Returns the flag value wrapped in the Optional class. */
+  public Optional<String> getFlagValue(String flagName) {
+    return Optional.ofNullable(flags.get(flagName));
+  }
+
+  /**
+   * Returns a flag value. If absent throws IllegalStateException.
+   *
+   * @param flagName name of the flag to fetch
+   * @return string, the value of the flag
+   * @throws IllegalStateException if the flag was not set.
+   */
+  public String getRequiredFlagValue(String flagName) {
+    return getFlagValue(flagName)
+        .orElseThrow(
+            () ->
+                new IllegalArgumentException(
+                    String.format("Missing the required --%s flag.", flagName)));
+  }
+
+  /** Returns the string value of the flag or the default if has not been set. */
+  public String getFlagValueOrDefault(String flagName, String defaultValue) {
+    return flags.getOrDefault(flagName, defaultValue);
+  }
+
+  public Optional<Path> getFlagValueAsPath(String flagName) {
+    return Optional.ofNullable(flags.get(flagName)).map(Paths::get);
+  }
+
+  /**
+   * Returns the value of the flag as list of strings.
+   *
+   * <p>It converts the string flag value to the list assuming it's delimited by a comma. The list
+   * is empty if the flag has not been set.
+   */
+  public List<String> getFlagListValue(String flagName) {
+    if (!isFlagSet(flagName)) {
+      return Collections.emptyList();
+    }
+    return Arrays.asList(flags.get(flagName).split(","));
+  }
+
+  /**
+   * Returns the list of commands that were parsed.
+   *
+   * @return the immutable list of commands.
+   */
+  public List<String> getCommands() {
+    return Collections.unmodifiableList(commands);
+  }
+
+  /** Exception encapsulating any flag parsing errors. */
+  public static class ParseException extends RuntimeException {
+
+    public ParseException(String message) {
+      super(message);
+    }
+  }
+}
diff --git a/bundletool/manifest.txt b/bundletool/manifest.txt
new file mode 100644
index 0000000..b8a4c92
--- /dev/null
+++ b/bundletool/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.tools.appbundle.bundletool.BundleToolMain