Initial, fledgling implementation of bundle2installable tool.

Test: e2e.sh

Change-Id: I30c032ffca0908bf5d81869d563308ed0ff30aab
diff --git a/bundle2installable/.idea/scopes/scope_settings.xml b/bundle2installable/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/bundle2installable/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+  <state>
+    <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/bundle2installable/Android.mk b/bundle2installable/Android.mk
new file mode 100644
index 0000000..b9e9f72
--- /dev/null
+++ b/bundle2installable/Android.mk
@@ -0,0 +1,49 @@
+LOCAL_PATH:= $(call my-dir)
+
+## bundletool script
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable
+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)/bundle2installable$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/bundle2installable | $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+## proto library
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable-protos
+LOCAL_MODULE_TAG := optional
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_SRC_FILES := $(call all-proto-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-full
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+## tool jar
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := bundle2installable-protos jsr305lib
+
+LOCAL_JAR_MANIFEST := manifest.txt
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+
diff --git a/bundle2installable/bundle2installable.iml b/bundle2installable/bundle2installable.iml
new file mode 100644
index 0000000..7df2ab0
--- /dev/null
+++ b/bundle2installable/bundle2installable.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
+    </content>
+    <content url="file://$MODULE_DIR$/../../../out/target/common/obj/APPS/bundle2installable_intermediates/src">
+      <sourceFolder url="file://$MODULE_DIR$/../../../out/target/common/obj/APPS/bundle2installable_intermediates/src" isTestSource="false" />
+    </content>
+
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" module-name="jsr305" />
+    <orderEntry type="library" module-name="protobuf" />
+
+  </component>
+</module>
+
diff --git a/bundle2installable/e2e.sh b/bundle2installable/e2e.sh
new file mode 100755
index 0000000..36cf8cc
--- /dev/null
+++ b/bundle2installable/e2e.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+TOOL_CMD=../../../out/host/linux-x86/bin/bundle2installable
+
+# set up directories
+mkdir -p output/
+mkdir -p output-splits/
+mkdir -p output-deliverables/
+
+# clean up
+rm -fr output/*
+rm -fr output-splits/*
+rm -fr output-deliverables/*
+
+echo "--------------------------------------------------------------------"
+echo "Generating module zips"
+echo "--------------------------------------------------------------------"
+$TOOL_CMD generate example/bundle/bundle.zip output
+echo "--------------------------------------------------------------------"
+
+for MODULEZIP in `ls output`;
+do
+  echo "Splitting module: $MODULEZIP"
+  echo "--------------------------------------------------------------------"
+  $TOOL_CMD split-module output/$MODULEZIP output-splits/
+  echo "--------------------------------------------------------------------"
+done
+
+echo "Linking deliverable: x86,res-default,gl1"
+echo "--------------------------------------------------------------------"
+$TOOL_CMD link output-deliverables/deliverable.zip output-splits --modules=module1,module2,module3 --splits=x86,res-default,gl1
+
diff --git a/bundle2installable/etc/bundle2installable b/bundle2installable/etc/bundle2installable
new file mode 100644
index 0000000..ac7cac3
--- /dev/null
+++ b/bundle2installable/etc/bundle2installable
@@ -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/bundle2installable/etc/bundle2installable.bat b/bundle2installable/etc/bundle2installable.bat
new file mode 100644
index 0000000..09a9d36
--- /dev/null
+++ b/bundle2installable/etc/bundle2installable.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/bundle2installable/example/bundle/bundle.zip b/bundle2installable/example/bundle/bundle.zip
new file mode 100644
index 0000000..21cb2f2
--- /dev/null
+++ b/bundle2installable/example/bundle/bundle.zip
Binary files differ
diff --git a/bundle2installable/example/bundle/module1/assets/some_file.txt b/bundle2installable/example/bundle/module1/assets/some_file.txt
new file mode 100644
index 0000000..a984be3
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/assets/some_file.txt
@@ -0,0 +1 @@
+Some asset.
diff --git a/bundle2installable/example/bundle/module1/format.textpb b/bundle2installable/example/bundle/module1/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/format.textpb
@@ -0,0 +1,4 @@
+packages {
+  package_id: 128
+  package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg b/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg
new file mode 100644
index 0000000..769a602
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg
@@ -0,0 +1 @@
+hdpi jpeg
diff --git a/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg
new file mode 100644
index 0000000..ffa7a30
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg
@@ -0,0 +1 @@
+Mpdi jpeg
diff --git a/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg
new file mode 100644
index 0000000..9f4d332
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg
@@ -0,0 +1 @@
+mdpi res2
diff --git a/bundle2installable/example/bundle/module2/format.textpb b/bundle2installable/example/bundle/module2/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/format.textpb
@@ -0,0 +1,4 @@
+packages {
+  package_id: 128
+  package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so b/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so
new file mode 100644
index 0000000..3391b8b
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so
@@ -0,0 +1 @@
+arm64-v8 library
diff --git a/bundle2installable/example/bundle/module2/native/x86/alibrary.so b/bundle2installable/example/bundle/module2/native/x86/alibrary.so
new file mode 100644
index 0000000..d758c3d
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/native/x86/alibrary.so
@@ -0,0 +1 @@
+x86 library
diff --git a/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg
new file mode 100644
index 0000000..1d83daf
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg
@@ -0,0 +1 @@
+hdpi a.jpg
diff --git a/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg
new file mode 100644
index 0000000..94213bf
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg
@@ -0,0 +1 @@
+mdpi a.jpg
diff --git a/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg
new file mode 100644
index 0000000..40fb343
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg
@@ -0,0 +1 @@
+xhdpi a.jpg
diff --git a/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1
new file mode 100644
index 0000000..2849ff7
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1
@@ -0,0 +1 @@
+textures for gl1
diff --git a/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1
new file mode 100644
index 0000000..2c853ce
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1
@@ -0,0 +1 @@
+textures for gl2
diff --git a/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1
new file mode 100644
index 0000000..cf30c2c
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1
@@ -0,0 +1 @@
+textures for gl3
diff --git a/bundle2installable/example/bundle/module3/format.textpb b/bundle2installable/example/bundle/module3/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/format.textpb
@@ -0,0 +1,4 @@
+packages {
+  package_id: 128
+  package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so b/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so
new file mode 100644
index 0000000..826009f
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so
@@ -0,0 +1 @@
+module3 arm64-v8 lib1.so
diff --git a/bundle2installable/example/bundle/module3/native/x86/lib1.so b/bundle2installable/example/bundle/module3/native/x86/lib1.so
new file mode 100644
index 0000000..90e549c
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/native/x86/lib1.so
@@ -0,0 +1 @@
+module3 lib1.so x86
diff --git a/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg b/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg
new file mode 100644
index 0000000..2a2f661
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg
@@ -0,0 +1 @@
+module3 hdpi a.jpg
diff --git a/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg b/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg
new file mode 100644
index 0000000..2b04ec0
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg
@@ -0,0 +1 @@
+module3 mdpi a.jpg
diff --git a/bundle2installable/example/module/assets/gl1/textures.etc1 b/bundle2installable/example/module/assets/gl1/textures.etc1
new file mode 100644
index 0000000..4fa3fae
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl1/textures.etc1
@@ -0,0 +1 @@
+gl1-textures
diff --git a/bundle2installable/example/module/assets/gl2/textures.etc1 b/bundle2installable/example/module/assets/gl2/textures.etc1
new file mode 100644
index 0000000..7bd0987
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl2/textures.etc1
@@ -0,0 +1 @@
+gl2-textures
diff --git a/bundle2installable/example/module/assets/gl3/textures.etc1 b/bundle2installable/example/module/assets/gl3/textures.etc1
new file mode 100644
index 0000000..d88e8a7
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl3/textures.etc1
@@ -0,0 +1 @@
+gl3-textures
diff --git a/bundle2installable/example/module/assets/textures.etc1 b/bundle2installable/example/module/assets/textures.etc1
new file mode 100644
index 0000000..3b6f771
--- /dev/null
+++ b/bundle2installable/example/module/assets/textures.etc1
@@ -0,0 +1 @@
+no-variant-textures
diff --git a/bundle2installable/example/module/format.textpb b/bundle2installable/example/module/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/module/format.textpb
@@ -0,0 +1,4 @@
+packages {
+  package_id: 128
+  package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/module/module.zip b/bundle2installable/example/module/module.zip
new file mode 100644
index 0000000..7b00859
--- /dev/null
+++ b/bundle2installable/example/module/module.zip
Binary files differ
diff --git a/bundle2installable/example/module/native/arm64-v8/library.so b/bundle2installable/example/module/native/arm64-v8/library.so
new file mode 100644
index 0000000..3391b8b
--- /dev/null
+++ b/bundle2installable/example/module/native/arm64-v8/library.so
@@ -0,0 +1 @@
+arm64-v8 library
diff --git a/bundle2installable/example/module/native/x86/library.so b/bundle2installable/example/module/native/x86/library.so
new file mode 100644
index 0000000..ffc9498
--- /dev/null
+++ b/bundle2installable/example/module/native/x86/library.so
@@ -0,0 +1 @@
+x86-library
diff --git a/bundle2installable/example/module/res/drawable-hdpi/picture.png b/bundle2installable/example/module/res/drawable-hdpi/picture.png
new file mode 100644
index 0000000..520659f
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable-hdpi/picture.png
@@ -0,0 +1 @@
+hdpi
diff --git a/bundle2installable/example/module/res/drawable-mdpi/picture.png b/bundle2installable/example/module/res/drawable-mdpi/picture.png
new file mode 100644
index 0000000..d3e3b70
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable-mdpi/picture.png
@@ -0,0 +1 @@
+mdpi
diff --git a/bundle2installable/example/module/res/drawable/picture.png b/bundle2installable/example/module/res/drawable/picture.png
new file mode 100644
index 0000000..55cb19a
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable/picture.png
@@ -0,0 +1 @@
+fallback
diff --git a/bundle2installable/manifest.txt b/bundle2installable/manifest.txt
new file mode 100644
index 0000000..b00d463
--- /dev/null
+++ b/bundle2installable/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.bundle.tool.Bundle2InstallableMain
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java
new file mode 100644
index 0000000..1cbd5cb
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java
@@ -0,0 +1,103 @@
+/*
+ * 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.bundle2installable;
+
+import static com.android.tools.appbundle.bundle2installable.util.PathUtils.getFilePathWithoutTopLevel;
+import static com.android.tools.appbundle.bundle2installable.util.PathUtils.getFilePathWithoutVariant;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Assets directory variant processor.
+ *
+ * Currently, scans the directory in the assets directory of the module zip. Every directory name
+ * is considered a variant name, and the final split will contain files from the variant directory
+ * with that directory elided: all goes directly in the assets directory. Each variant will include
+ * files that are directly in the assets root directory.
+ */
+public class AssetsProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+  private static final String ASSETS_DIR = "assets";
+
+  private Map<String, List<ZipEntry>> variantAssetsMap;
+  private List<ZipEntry> nonVariantAssets;
+
+  public AssetsProcessor() {
+    this.variantAssetsMap = new HashMap<>();
+    this.nonVariantAssets = new ArrayList<>();
+  }
+
+  public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+    // TODO(b/64285122): fix the asset variants detection when there is only non-variant data.
+    Path p = Paths.get(entry.getName());
+    if (PathUtils.isInsideTopDirectory(p, ASSETS_DIR)) {
+      String variant = PathUtils.resolveVariant(p);
+      if (variant != null) { // inside the variant directory
+        if (!variantAssetsMap.containsKey(variant)) {
+          variantAssetsMap.put(variant, new ArrayList<>());
+        }
+        variantAssetsMap.get(variant).add(entry);
+      } else { // inside the assets directory
+        System.out.println("Entry name: " + entry.getName());
+        if (!entry.isDirectory()) {
+          System.out.println("..is not a directory");
+          nonVariantAssets.add(entry);
+        }
+      }
+    }
+  }
+
+  public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+    List<Pair<ZipEntry, String>> res = new ArrayList<>();
+    Set<String> coveredFiles = new HashSet<>();
+    if (!variantAssetsMap.containsKey(variant)) {
+      System.out.println(
+          String.format("Warning! The '%s' variant has no dedicated assets.",
+              variant));
+    } else {
+      for (ZipEntry entry : variantAssetsMap.get(variant)) {
+        String pathWithoutVariant = getFilePathWithoutVariant(entry.getName());
+        coveredFiles.add(pathWithoutVariant);
+        res.add(Pair.of(entry, Paths.get(ASSETS_DIR, pathWithoutVariant).toString()));
+      }
+    }
+    for (ZipEntry entry : nonVariantAssets) {
+      String pathWithoutVariant = getFilePathWithoutTopLevel(entry.getName());
+      if (!coveredFiles.contains(pathWithoutVariant)) {
+        res.add(Pair.of(entry, Paths.get(ASSETS_DIR, pathWithoutVariant).toString()));
+      }
+    }
+    return res;
+  }
+
+  public List<String> getAllVariants() {
+    return new ArrayList<>(variantAssetsMap.keySet());
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java
new file mode 100644
index 0000000..71b1111
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java
@@ -0,0 +1,56 @@
+/*
+ * 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.bundle2installable;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Main entry point of the bundle2installable tool
+ */
+public class Bundle2InstallableMain {
+
+  public static void main(String[] args) throws IOException {
+    if (args.length < 1) {
+      throw new IllegalStateException(
+          "Incorrect number of args. Use bundle2installable [command] ...");
+    }
+    String command = args[0];
+    switch (command) {
+      case "split-module":
+        ModuleSplitter.doSplitModule(args);
+        break;
+      case "generate":
+        ModuleZipMaker.generateModuleZips(args[1], args[2]);
+        break;
+      case "link":
+        DeliverableLinker.makeDeliverable(args[1], args[2], makeList(args[3]),
+            makeList(args[4]));
+        break;
+    }
+  }
+
+  private static List<String> makeList(String flag) {
+    String[] equal = flag.split("=");
+    String equalVal = equal[0];
+    if (equal.length > 1) { // assignment
+      equalVal = equal[1];
+    }
+    return Arrays.asList(equalVal.split(","));
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java
new file mode 100644
index 0000000..b42910b
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java
@@ -0,0 +1,51 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * A linker that can create an installable from the module splits.
+ */
+public class DeliverableLinker {
+
+  public static void makeDeliverable(String deliverablePath, String splitDirectory,
+      List<String> modules, List<String> splits) throws IOException {
+    ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(deliverablePath));
+    for (String module : modules) {
+      for (String split : splits) {
+        Path p = Paths.get(splitDirectory, PathUtils.makeSplitName(module, split));
+        if (Files.isRegularFile(p)) {
+          System.out.println(String.format("Linking %s", p.toString()));
+          ZipUtils.writeEntry(PathUtils.stripDirectories(p.toString()), outputStream,
+              new FileInputStream(p.toString()));
+        }
+      }
+    }
+    outputStream.close();
+  }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java
new file mode 100644
index 0000000..b15bb35
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+
+/**
+ * Implementation of the split-module command.
+ */
+public class ModuleSplitter {
+
+  public static void doSplitModule(String[] args) throws IOException {
+    if (args.length < 3) {
+      throw new IllegalStateException(
+          "Incorrect number of args. Use bundle2installable split-module [module.zip] [output-dir]");
+    }
+    String moduleFilename = args[1];
+    String outputFilename = args[2];
+
+    ZipWalker visitor = new ZipWalker(moduleFilename);
+    ResourcesProcessor resourcesProcessor = new ResourcesProcessor();
+    AssetsProcessor assetsProcessor = new AssetsProcessor();
+    NativeProcessor nativeProcessor = new NativeProcessor();
+    visitor.addListener(resourcesProcessor);
+    visitor.addListener(assetsProcessor);
+    visitor.addListener(nativeProcessor);
+    visitor.walk();
+
+    System.out.println("Detected asset variants: " + assetsProcessor.getAllVariants().toString());
+    System.out.println("Detected native variants: " + nativeProcessor.getAllVariants().toString());
+    System.out
+        .println("Detected resource variants: " + resourcesProcessor.getAllVariants().toString());
+
+    for (String variant : assetsProcessor.getAllVariants()) {
+      String splitName = outputFilename + "/" + PathUtils
+          .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-" + variant + ".zip";
+      PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+      outGen.writePreSplit(visitor.getZipFile(), assetsProcessor.generateForVariant(variant));
+    }
+    for (String variant : nativeProcessor.getAllVariants()) {
+      String splitName = outputFilename + "/" + PathUtils
+          .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-" + variant + ".zip";
+      PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+      outGen.writePreSplit(visitor.getZipFile(), nativeProcessor.generateForVariant(variant));
+    }
+    for (String variant : resourcesProcessor.getAllVariants()) {
+      String splitName = outputFilename + "/" + PathUtils
+          .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-res-" + variant + ".zip";
+      PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+      outGen.writePreSplit(visitor.getZipFile(), resourcesProcessor.generateForVariant(variant));
+    }
+  }
+
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java
new file mode 100644
index 0000000..0b5643a
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java
@@ -0,0 +1,87 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Class that takes a bundle and outputs the zipped modules from it.
+ *
+ * As modules are being processed separately, this will put them in the right
+ * input format.
+ *
+ * By default it outputs all modules contained in the bundle.
+ */
+public class ModuleZipMaker {
+
+  public static void generateModuleZips(String bundleFile, String outputDirectory)
+      throws IOException {
+    ZipFile bundleZip = new ZipFile(bundleFile);
+    Enumeration<? extends ZipEntry> e = bundleZip.entries();
+    Map<String, List<ZipEntry>> moduleEntries = new HashMap<>();
+    ZipEntry entry;
+    while (e.hasMoreElements()) {
+      entry = e.nextElement();
+      System.out.println(String.format("Entry: %s", entry.getName()));
+      Path p = Paths.get(entry.getName());
+      if (p.getNameCount() > 1) {
+        String moduleName = p.getName(0).toString();
+        if (!moduleEntries.containsKey(moduleName)) {
+          moduleEntries.put(moduleName, new ArrayList<ZipEntry>());
+        }
+        if (!entry.isDirectory()) {
+          moduleEntries.get(moduleName).add(entry);
+        }
+      }
+    }
+    for (String moduleDirectory : moduleEntries.keySet()) {
+      System.out.println(String.format("Module: %s", moduleDirectory));
+      for (ZipEntry moduleEntry : moduleEntries.get(moduleDirectory)) {
+        System.out.println(String.format("Entry : %s", moduleEntry.getName()));
+      }
+      deflateModule(Paths.get(outputDirectory, moduleDirectory + ".zip").toString(),
+          bundleZip, moduleEntries.get(moduleDirectory));
+    }
+  }
+
+  private static void deflateModule(String moduleZipOutput, ZipFile bundleFile,
+      List<ZipEntry> entries) throws IOException {
+    ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(moduleZipOutput));
+    for (ZipEntry entry : entries) {
+      ZipUtils.writeEntry(PathUtils.getFilePathWithoutTopLevel(entry.getName()), outputStream,
+          bundleFile.getInputStream(entry));
+    }
+    outputStream.close();
+  }
+
+  private static boolean isModuleDirectory(ZipEntry entry) {
+    throw new UnsupportedOperationException("Not implemented yet");
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java
new file mode 100644
index 0000000..e742621
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Processes native library directory and discovers cpu variants.
+ *
+ * Currently, expects ABI to be expressed as a subdirectory name inside "native" directory.
+ * The libraries are outputted to the "lib" directory as per APK standard.
+ */
+public class NativeProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+  private static final String NATIVE_DIR = "native";
+  private static final String DESTINATION_DIR = "lib";
+
+  private Map<String, List<ZipEntry>> variantLibsMap;
+  private List<ZipEntry> nonVariantLibs;
+
+  public NativeProcessor() {
+    this.variantLibsMap = new HashMap<>();
+    this.nonVariantLibs = new ArrayList<>();
+  }
+
+  public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+    Path p = Paths.get(entry.getName());
+    if (PathUtils.isInsideTopDirectory(p, NATIVE_DIR)) {
+      String variant = PathUtils.resolveVariant(p);
+      if (variant != null) {
+        if (!variantLibsMap.containsKey(variant)) {
+          variantLibsMap.put(variant, new ArrayList<>());
+        }
+        variantLibsMap.get(variant).add(entry);
+      } else { // inside the libs directory
+        if (!entry.isDirectory()) {
+          // aapt2 doesn't strip any files in libs/ top-level. So we should
+          // include them in each variant as well.
+          nonVariantLibs.add(entry);
+        }
+      }
+    }
+  }
+
+  public List<String> getAllVariants() {
+    return new ArrayList<>(variantLibsMap.keySet());
+  }
+
+  /**
+   * Returns pair of zip entries and the destination path in the split for the variant
+   */
+  public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+    List<Pair<ZipEntry, String>> res = new ArrayList<>();
+    Set<String> coveredFiles = new HashSet<>();
+    if (!variantLibsMap.containsKey(variant)) {
+      throw new IllegalStateException(
+          String.format("Variant '%s' doesn't exist for the native libraries.", variant));
+    } else {
+      for (ZipEntry entry : variantLibsMap.get(variant)) {
+        String pathWithoutVariant = PathUtils.getFilePathWithoutVariant(entry.getName());
+        coveredFiles.add(pathWithoutVariant);
+        // our destination path needs to include architecture/variant though
+        Path destinationPath = Paths.get(DESTINATION_DIR, variant, pathWithoutVariant);
+        res.add(Pair.of(entry, destinationPath.toString()));
+      }
+    }
+    for (ZipEntry entry : nonVariantLibs) {
+      String pathWithoutVariant = PathUtils.getFilePathWithoutTopLevel(entry.getName());
+      if (!coveredFiles.contains(pathWithoutVariant)) {
+        res.add(Pair.of(entry, Paths.get(DESTINATION_DIR, pathWithoutVariant).toString()));
+      }
+    }
+    return res;
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java
new file mode 100644
index 0000000..b646ef9
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Generates a zip file with assets, resources and libs in a correct place.
+ *
+ * This is not yet APK split as it has to be processed by the aapt2.
+ */
+public class PreSplitGenerator {
+
+  private ZipOutputStream outputStream;
+
+  PreSplitGenerator(String outputFile) throws FileNotFoundException {
+    this.outputStream = new ZipOutputStream(new FileOutputStream(outputFile));
+  }
+
+  public void writePreSplit(ZipFile moduleZip, List<Pair<ZipEntry, String>> entriesToWrite)
+      throws IOException, FileNotFoundException {
+    for (Pair<ZipEntry, String> entry : entriesToWrite) {
+      ZipUtils.writeEntry(entry.getSecond(), outputStream,
+          moduleZip.getInputStream(entry.getFirst()));
+    }
+    outputStream.close();
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java
new file mode 100644
index 0000000..ffd7264
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java
@@ -0,0 +1,79 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.ProtoUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import com.android.tools.aapt2.FormatProto;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Detects variants in the res directory of the module zip.
+ *
+ * Currently, this class detects only one variant that will contain all resources.
+ */
+public class ResourcesProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+  public static final String FORMAT_PROTO_NAME = "format.textpb";
+  public static final String RES_DIRECTORY = "res/";
+
+  /**
+   * For now we just copy all resources
+   */
+  private List<ZipEntry> allVariants;
+
+  public ResourcesProcessor() {
+    this.allVariants = new ArrayList<ZipEntry>();
+  }
+
+  public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+    if (entry.getName().equals(FORMAT_PROTO_NAME)) {
+      FormatProto.ResourceTable resourceProto = ProtoUtils.extractResourceProto(bundle, entry);
+      // ... processing
+    }
+    if (entry.getName().startsWith(RES_DIRECTORY)) {
+      if (!entry.isDirectory()) {
+        allVariants.add(entry);
+      }
+    }
+  }
+
+  /**
+   * For now, just generate everything
+   */
+  public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+    List<Pair<ZipEntry, String>> res = new ArrayList<>();
+    for (ZipEntry entry : allVariants) {
+      res.add(Pair.of(entry, entry.getName()));
+    }
+    return res;
+  }
+
+  /**
+   * Recognizes only one default variant
+   */
+  public List<String> getAllVariants() {
+    return Arrays.asList("default");
+  }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java
new file mode 100644
index 0000000..c7bde25
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+/**
+ * Interface that describes ability to generate a set of files in the resulting split APK for a
+ * given variant. Currently, the notion of a variant is very simplified: one dimension, and a
+ * string.
+ */
+public interface VariantProcessor {
+
+  /**
+   * Returns the list of files from the module zip to be included in the split for a variant.
+   *
+   * @param variant a simple string representing a variant, currently we support only one
+   * dimension.
+   * @return A list of pairs: {@code ZipEntry} from the module zip to be included in the split,
+   * {@code string} destination path in the split.
+   */
+  List<Pair<ZipEntry, String>> generateForVariant(String variant);
+
+  /**
+   * Returns list of all variants detected by this processor.
+   */
+  List<String> getAllVariants();
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java
new file mode 100644
index 0000000..26b7e72
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java
@@ -0,0 +1,82 @@
+/*
+ * 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.bundle2installable.util;
+
+/**
+ * Pair of two elements.
+ */
+public final class Pair<A, B> {
+
+  private final A mFirst;
+  private final B mSecond;
+
+  private Pair(A first, B second) {
+    mFirst = first;
+    mSecond = second;
+  }
+
+  public static <A, B> Pair<A, B> of(A first, B second) {
+    return new Pair<A, B>(first, second);
+  }
+
+  public A getFirst() {
+    return mFirst;
+  }
+
+  public B getSecond() {
+    return mSecond;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
+    result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return false;
+    }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Pair other = (Pair) obj;
+    if (mFirst == null) {
+      if (other.mFirst != null) {
+        return false;
+      }
+    } else if (!mFirst.equals(other.mFirst)) {
+      return false;
+    }
+    if (mSecond == null) {
+      if (other.mSecond != null) {
+        return false;
+      }
+    } else if (!mSecond.equals(other.mSecond)) {
+      return false;
+    }
+    return true;
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java
new file mode 100644
index 0000000..0169525
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java
@@ -0,0 +1,72 @@
+/*
+ * 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.bundle2installable.util;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.annotation.Nullable;
+
+/**
+ * Various utilities for path processing.
+ */
+public class PathUtils {
+
+  private static final int TOP_LEVEL = 0;
+  private static final int VARIANT_LEVEL = 1;
+  private static final int LEVEL_IN_VARIANT = 2;
+  private static final int LEVEL_IN_DEFAULT = 1;
+
+  public static String stripExtension(String filename) {
+    return filename.substring(0, filename.lastIndexOf('.'));
+  }
+
+  public static String makeSplitName(String module, String split) {
+    return module + "-" + split + ".zip";
+  }
+
+  public static String stripDirectories(String fullPath) {
+    Path p = Paths.get(fullPath);
+    return p.getName(p.getNameCount() - 1).toString();
+  }
+
+  public static boolean isInsideTopDirectory(Path p, String directoryName) {
+    return p.getNameCount() > 1 && p.getName(TOP_LEVEL).toString().equals(directoryName);
+  }
+
+  @Nullable
+  public static String resolveVariant(Path p) {
+    if (p.getNameCount() > 2) { // inside the variant directory
+      return p.getName(VARIANT_LEVEL).toString();
+    }
+    return null;
+  }
+
+  public static String getFilePathWithoutVariant(String entryPath) {
+    return removeTopLevelPath(entryPath, LEVEL_IN_VARIANT);
+  }
+
+  public static String getFilePathWithoutTopLevel(String entryPath) {
+    return removeTopLevelPath(entryPath, LEVEL_IN_DEFAULT);
+  }
+
+  public static String removeTopLevelPath(String entryPath, int numLevels) {
+    Path p = Paths.get(entryPath);
+    System.out.println(entryPath);
+    return p.subpath(numLevels, p.getNameCount()).toString();
+  }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java
new file mode 100644
index 0000000..6d0c787
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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.bundle2installable.util;
+
+import com.android.tools.aapt2.FormatProto;
+import com.google.protobuf.TextFormat;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Protobuf utility functions used throughout the tool.
+ */
+public class ProtoUtils {
+
+  /**
+   * Reads format.proto from the aapt2 output. For now using the textproto format.
+   */
+  public static FormatProto.ResourceTable extractResourceProto(ZipFile bundle, ZipEntry entry)
+      throws IOException {
+    System.out.println(String.format("Extracting %s", entry.getName()));
+
+    FormatProto.ResourceTable.Builder tableBuilder = FormatProto.ResourceTable.newBuilder();
+    TextFormat.merge(new InputStreamReader(bundle.getInputStream(entry)), tableBuilder);
+    return tableBuilder.build();
+  }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java
new file mode 100644
index 0000000..ae4b5ac
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.bundle2installable.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Various zip utilities to avoid code duplication.
+ */
+public class ZipUtils {
+
+  private static final int BUFFER = 2048;
+
+  public static void writeEntry(String entryName, ZipOutputStream outputStream,
+      InputStream inputStream) throws IOException {
+    outputStream.putNextEntry(new ZipEntry(entryName));
+    byte data[] = new byte[BUFFER];
+    int count;
+    while ((count = inputStream.read(data, 0, BUFFER)) != -1) {
+      outputStream.write(data, 0, count);
+    }
+    inputStream.close();
+    outputStream.closeEntry();
+  }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java
new file mode 100644
index 0000000..71c3060
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java
@@ -0,0 +1,63 @@
+/*
+ * 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.bundle2installable.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ZipWalker {
+
+  private String zipFilename;
+  private List<ZipEntryListener> listeners;
+  private ZipFile zipFile;
+
+  public ZipWalker(String zipFilename) {
+    this.zipFilename = zipFilename;
+    this.listeners = new ArrayList<ZipEntryListener>();
+    this.zipFile = null;
+  }
+
+  public void walk() throws IOException {
+    zipFile = new ZipFile(zipFilename);
+    Enumeration<? extends ZipEntry> e = zipFile.entries();
+    ZipEntry entry;
+    while (e.hasMoreElements()) {
+      entry = e.nextElement();
+      System.out.println(String.format("Entry : %s", entry.getName()));
+      for (ZipEntryListener listener : listeners) {
+        listener.notify(zipFile, entry);
+      }
+    }
+  }
+
+  public void addListener(ZipEntryListener listener) {
+    listeners.add(listener);
+  }
+
+  public ZipFile getZipFile() {
+    return zipFile;
+  }
+
+  public interface ZipEntryListener {
+
+    void notify(ZipFile bundle, ZipEntry entry) throws IOException;
+  }
+}
diff --git a/bundle2installable/src/proto/Format.proto b/bundle2installable/src/proto/Format.proto
new file mode 100644
index 0000000..8b0f81d
--- /dev/null
+++ b/bundle2installable/src/proto/Format.proto
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto2";
+
+// option optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+
+option java_package = "com.android.tools.aapt2";
+option java_outer_classname = "FormatProto";
+
+message ConfigDescription {
+	optional bytes data = 1;
+	optional string product = 2;
+}
+
+message StringPool {
+	optional bytes data = 1;
+}
+
+message CompiledFile {
+	message Symbol {
+		optional string resource_name = 1;
+		optional uint32 line_no = 2;
+	}
+
+	optional string resource_name = 1;
+	optional ConfigDescription config = 2;
+	optional string source_path = 3;
+	repeated Symbol exported_symbols = 4;
+}
+
+message ResourceTable {
+	optional StringPool string_pool = 1;
+	optional StringPool source_pool = 2;
+	optional StringPool symbol_pool = 3;
+	repeated Package packages = 4;
+}
+
+message Package {
+	optional uint32 package_id = 1;
+	optional string package_name = 2;
+	repeated Type types = 3;
+}
+
+message Type {	
+	optional uint32 id = 1;
+	optional string name = 2;
+	repeated Entry entries = 3;
+}
+
+message SymbolStatus {
+	enum Visibility {
+		Unknown = 0;
+		Private = 1;
+		Public = 2;
+	}
+	optional Visibility visibility = 1;
+	optional Source source = 2;
+	optional string comment = 3;
+	optional bool allow_new = 4;
+}
+
+message Entry {
+	optional uint32 id = 1;
+	optional string name = 2;
+	optional SymbolStatus symbol_status = 3;
+	repeated ConfigValue config_values = 4;
+}
+
+message ConfigValue {
+	optional ConfigDescription config = 1;
+	optional Value value = 2;
+}
+
+message Source {
+	optional uint32 path_idx = 1;
+	optional uint32 line_no = 2;
+	optional uint32 col_no = 3;
+}
+
+message Reference {
+	enum Type {
+		Ref = 0;
+		Attr = 1;
+	}
+	optional Type type = 1;
+	optional uint32 id = 2;
+	optional uint32 symbol_idx = 3;
+	optional bool private = 4;
+}
+
+message Id {
+}
+
+message XString {
+	optional uint32 idx = 1;
+}
+
+message RawString {
+	optional uint32 idx = 1;
+}
+
+message FileReference {
+	optional uint32 path_idx = 1;
+}
+
+message Primitive {
+	optional uint32 type = 1;
+	optional uint32 data = 2;
+}
+
+message Attribute {
+	message Symbol {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference name = 3;
+		optional uint32 value = 4;
+	}
+	optional uint32 format_flags = 1;
+	optional int32 min_int = 2;
+	optional int32 max_int = 3;
+	repeated Symbol symbols = 4;
+}
+
+message Style {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference key = 3;
+		optional Item item = 4;
+	}
+
+	optional Reference parent = 1;
+	optional Source parent_source = 2;
+	repeated Entry entries = 3;
+}
+
+message Styleable {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference attr = 3;
+	}
+	repeated Entry entries = 1;
+}
+
+message Array {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Item item = 3;
+	}
+	repeated Entry entries = 1;
+}
+
+message Plural {
+	enum Arity {
+		Zero = 0;
+		One = 1;
+		Two = 2;
+		Few = 3;
+		Many = 4;
+		Other = 5;
+	}
+		
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Arity arity = 3;
+		optional Item item = 4;
+	}
+	repeated Entry entries = 1;
+}
+
+message Item {
+	optional Reference ref = 1;
+	optional XString str = 2;
+	optional RawString raw_str = 3;
+	optional FileReference file = 4;
+	optional Id id = 5;
+	optional Primitive prim = 6;
+}
+
+message CompoundValue {
+	optional Attribute attr = 1;
+	optional Style style = 2;
+	optional Styleable styleable = 3;
+	optional Array array = 4;
+	optional Plural plural = 5;
+}
+
+message Value {
+	optional Source source = 1;
+	optional string comment = 2;
+	optional bool weak = 3;
+	
+	optional Item item = 4;
+	optional CompoundValue compound_value = 5;	
+}