Merge "Include vendor partitions in target zip generation"
diff --git a/core/binary.mk b/core/binary.mk
index c2e3069..4736f06 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -654,59 +654,57 @@
 ## Compile the .proto files to .cc (or .c) and then to .o
 ###########################################################
 proto_sources := $(filter %.proto,$(my_src_files))
-proto_generated_objects :=
-proto_generated_headers :=
 ifneq ($(proto_sources),)
-proto_generated_sources_dir := $(generated_sources_dir)/proto
-proto_generated_obj_dir := $(intermediates)/proto
+proto_gen_dir := $(generated_sources_dir)/proto
 proto_sources_fullpath := $(addprefix $(LOCAL_PATH)/, $(proto_sources))
 
+my_rename_cpp_ext :=
 ifneq (,$(filter nanopb-c nanopb-c-enable_malloc, $(LOCAL_PROTOC_OPTIMIZE_TYPE)))
 my_proto_source_suffix := .c
 my_proto_c_includes := external/nanopb-c
-my_protoc_flags := --nanopb_out=$(proto_generated_sources_dir) \
+my_protoc_flags := --nanopb_out=$(proto_gen_dir) \
     --plugin=external/nanopb-c/generator/protoc-gen-nanopb
 my_protoc_deps := $(NANOPB_SRCS) $(proto_sources_fullpath:%.proto=%.options)
 else
-my_proto_source_suffix := .cc
+my_proto_source_suffix := $(LOCAL_CPP_EXTENSION)
+ifneq ($(my_proto_source_suffix),.cc)
+# aprotoc is hardcoded to write out only .cc file.
+# We need to rename the extension to $(LOCAL_CPP_EXTENSION) if it's not .cc.
+my_rename_cpp_ext := true
+endif
 my_proto_c_includes := external/protobuf/src
 my_cflags += -DGOOGLE_PROTOBUF_NO_RTTI
-my_protoc_flags := --cpp_out=$(proto_generated_sources_dir)
+my_protoc_flags := --cpp_out=$(proto_gen_dir)
 my_protoc_deps :=
 endif
-my_proto_c_includes += $(proto_generated_sources_dir)
+my_proto_c_includes += $(proto_gen_dir)
 
-proto_generated_sources := $(addprefix $(proto_generated_sources_dir)/, \
+proto_generated_cpps := $(addprefix $(proto_gen_dir)/, \
     $(patsubst %.proto,%.pb$(my_proto_source_suffix),$(proto_sources_fullpath)))
-proto_generated_headers := $(patsubst %.pb$(my_proto_source_suffix),%.pb.h, $(proto_generated_sources))
-proto_generated_objects := $(addprefix $(proto_generated_obj_dir)/, \
-    $(patsubst %.proto,%.pb.o,$(proto_sources_fullpath)))
-$(call track-src-file-obj,$(proto_sources),$(proto_generated_objects))
 
 # Ensure the transform-proto-to-cc rule is only defined once in multilib build.
-ifndef $(my_prefix)_$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_proto_defined
-$(proto_generated_sources): PRIVATE_PROTO_INCLUDES := $(TOP)
-$(proto_generated_sources): PRIVATE_PROTOC_FLAGS := $(LOCAL_PROTOC_FLAGS) $(my_protoc_flags)
-$(proto_generated_sources): $(proto_generated_sources_dir)/%.pb$(my_proto_source_suffix): %.proto $(my_protoc_deps) $(PROTOC)
+ifndef $(my_host)$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_proto_defined
+$(proto_generated_cpps): PRIVATE_PROTO_INCLUDES := $(TOP)
+$(proto_generated_cpps): PRIVATE_PROTOC_FLAGS := $(LOCAL_PROTOC_FLAGS) $(my_protoc_flags)
+$(proto_generated_cpps): PRIVATE_RENAME_CPP_EXT := $(my_rename_cpp_ext)
+$(proto_generated_cpps): $(proto_gen_dir)/%.pb$(my_proto_source_suffix): %.proto $(my_protoc_deps) $(PROTOC)
 	$(transform-proto-to-cc)
 
-# This is just a dummy rule to make sure gmake doesn't skip updating the dependents.
-$(proto_generated_headers): $(proto_generated_sources_dir)/%.pb.h: $(proto_generated_sources_dir)/%.pb$(my_proto_source_suffix)
-	@echo "Updated header file $@."
-	$(hide) touch $@
-
-$(my_prefix)_$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_proto_defined := true
-endif  # transform-proto-to-cc rule included only once
-
-$(proto_generated_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
-$(proto_generated_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
-$(proto_generated_objects): $(proto_generated_obj_dir)/%.o: $(proto_generated_sources_dir)/%$(my_proto_source_suffix) $(proto_generated_headers)
-ifeq ($(my_proto_source_suffix),.c)
-	$(transform-$(PRIVATE_HOST)c-to-o)
-else
-	$(transform-$(PRIVATE_HOST)cpp-to-o)
+$(my_host)$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_proto_defined := true
 endif
-$(call include-depfiles-for-objs, $(proto_generated_objects))
+# Ideally we can generate the source directly into $(intermediates).
+# But many Android.mks assume the .pb.hs are in $(generated_sources_dir).
+# As a workaround, we make a copy in the $(intermediates).
+proto_intermediate_dir := $(intermediates)/proto
+proto_intermediate_cpps := $(patsubst $(proto_gen_dir)/%,$(proto_intermediate_dir)/%,\
+    $(proto_generated_cpps))
+$(proto_intermediate_cpps) : $(proto_intermediate_dir)/% : $(proto_gen_dir)/%
+	@echo "Copy: $@"
+	$(copy-file-to-target)
+	$(hide) cp $(basename $<).h $(basename $@).h
+$(call track-src-file-gen,$(proto_sources),$(proto_intermediate_cpps))
+
+my_generated_sources += $(proto_intermediate_cpps)
 
 my_c_includes += $(my_proto_c_includes)
 # Auto-export the generated proto source dir.
@@ -892,7 +890,7 @@
 dotdot_arm_objects :=
 $(foreach s,$(dotdot_arm_sources),\
   $(eval $(call compile-dotdot-cpp-file,$(s),\
-  $(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
+  $(my_additional_dependencies),\
   dotdot_arm_objects)))
 $(call track-src-file-obj,$(patsubst %,%.arm,$(dotdot_arm_sources)),$(dotdot_arm_objects))
 
@@ -900,7 +898,7 @@
 dotdot_objects :=
 $(foreach s,$(dotdot_sources),\
   $(eval $(call compile-dotdot-cpp-file,$(s),\
-    $(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
+    $(my_additional_dependencies),\
     dotdot_objects)))
 $(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects))
 
@@ -918,7 +916,6 @@
 ifneq ($(strip $(cpp_objects)),)
 $(cpp_objects): $(intermediates)/%.o: \
     $(TOPDIR)$(LOCAL_PATH)/%$(LOCAL_CPP_EXTENSION) \
-    $(yacc_cpps) $(proto_generated_headers) \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)cpp-to-o)
 $(call include-depfiles-for-objs, $(cpp_objects))
@@ -940,8 +937,7 @@
 $(gen_cpp_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
 $(gen_cpp_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
 $(gen_cpp_objects): $(intermediates)/%.o: \
-    $(intermediates)/%$(LOCAL_CPP_EXTENSION) $(yacc_cpps) \
-    $(proto_generated_headers) \
+    $(intermediates)/%$(LOCAL_CPP_EXTENSION) \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)cpp-to-o)
 $(call include-depfiles-for-objs, $(gen_cpp_objects))
@@ -996,7 +992,7 @@
 dotdot_arm_objects :=
 $(foreach s,$(dotdot_arm_sources),\
   $(eval $(call compile-dotdot-c-file,$(s),\
-    $(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
+    $(my_additional_dependencies),\
     dotdot_arm_objects)))
 $(call track-src-file-obj,$(patsubst %,%.arm,$(dotdot_arm_sources)),$(dotdot_arm_objects))
 
@@ -1004,7 +1000,7 @@
 dotdot_objects :=
 $(foreach s, $(dotdot_sources),\
   $(eval $(call compile-dotdot-c-file,$(s),\
-    $(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
+    $(my_additional_dependencies),\
     dotdot_objects)))
 $(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects))
 
@@ -1020,7 +1016,7 @@
 c_objects        := $(c_arm_objects) $(c_normal_objects)
 
 ifneq ($(strip $(c_objects)),)
-$(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c $(yacc_cpps) $(proto_generated_headers) \
+$(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)c-to-o)
 $(call include-depfiles-for-objs, $(c_objects))
@@ -1041,7 +1037,7 @@
 # TODO: support compiling certain generated files as arm.
 $(gen_c_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
 $(gen_c_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
-$(gen_c_objects): $(intermediates)/%.o: $(intermediates)/%.c $(yacc_cpps) $(proto_generated_headers) \
+$(gen_c_objects): $(intermediates)/%.o: $(intermediates)/%.c \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)c-to-o)
 $(call include-depfiles-for-objs, $(gen_c_objects))
@@ -1056,7 +1052,7 @@
 $(call track-src-file-obj,$(objc_sources),$(objc_objects))
 
 ifneq ($(strip $(objc_objects)),)
-$(objc_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.m $(yacc_cpps) $(proto_generated_headers) \
+$(objc_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.m \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)m-to-o)
 $(call include-depfiles-for-objs, $(objc_objects))
@@ -1071,7 +1067,7 @@
 $(call track-src-file-obj,$(objcpp_sources),$(objcpp_objects))
 
 ifneq ($(strip $(objcpp_objects)),)
-$(objcpp_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.mm $(yacc_cpps) $(proto_generated_headers) \
+$(objcpp_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.mm \
     $(my_additional_dependencies)
 	$(transform-$(PRIVATE_HOST)mm-to-o)
 $(call include-depfiles-for-objs, $(objcpp_objects))
@@ -1201,8 +1197,7 @@
     $(c_objects) \
     $(gen_c_objects) \
     $(objc_objects) \
-    $(objcpp_objects) \
-    $(proto_generated_objects)
+    $(objcpp_objects)
 
 new_order_normal_objects := $(foreach f,$(my_src_files),$(my_src_file_obj_$(f)))
 new_order_normal_objects += $(foreach f,$(my_gen_src_files),$(my_src_file_obj_$(f)))
@@ -1483,11 +1478,9 @@
    $(foreach l,$(LOCAL_EXPORT_STATIC_LIBRARY_HEADERS), \
      $(call intermediates-dir-for,STATIC_LIBRARIES,$(l),$(LOCAL_IS_HOST_MODULE),,$(LOCAL_2ND_ARCH_VAR_PREFIX),$(my_host_cross))/export_includes))
 $(export_includes): PRIVATE_REEXPORTED_INCLUDES := $(export_include_deps)
-# Make sure .pb.h are already generated before any dependent source files get compiled.
-# Similarly, the generated DBus headers need to exist before we export their location.
-# People are not going to consume the aidl generated cpp file, but the cpp file is
-# generated after the headers, so this is a convenient way to ensure the headers exist.
-$(export_includes) : $(proto_generated_headers) $(dbus_generated_headers) $(aidl_gen_cpp) $(export_include_deps)
+# By adding $(my_generated_sources) it makes sure the headers get generated
+# before any dependent source files get compiled.
+$(export_includes) : $(my_generated_sources) $(export_include_deps)
 	@echo Export includes file: $< -- $@
 	$(hide) mkdir -p $(dir $@) && rm -f $@.tmp && touch $@.tmp
 ifdef my_export_c_include_dirs
diff --git a/core/build-system.html b/core/build-system.html
index bddde6a..95f35ce 100644
--- a/core/build-system.html
+++ b/core/build-system.html
@@ -438,7 +438,7 @@
 GEN := $(intermediates)/<font color=red>file.c</font>
 $(GEN): PRIVATE_INPUT_FILE := $(LOCAL_PATH)/<font color=red>input.file</font>
 $(GEN): PRIVATE_CUSTOM_TOOL = <font color=red>cat $(PRIVATE_INPUT_FILE) &gt; $@</font>
-$(GEN): <font color=red>$(LOCAL_PATH)/file.c</font>
+$(GEN): <font color=red>$(LOCAL_PATH)/input.file</font>
 	$(transform-generated-source)
 LOCAL_GENERATED_SOURCES += $(GEN)
 </pre>
diff --git a/core/clang/HOST_x86_common.mk b/core/clang/HOST_x86_common.mk
index 0f4d4a2..1344eae 100644
--- a/core/clang/HOST_x86_common.mk
+++ b/core/clang/HOST_x86_common.mk
@@ -13,7 +13,8 @@
 ifeq ($(HOST_OS),linux)
 CLANG_CONFIG_x86_LINUX_HOST_EXTRA_ASFLAGS := \
   --gcc-toolchain=$($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG) \
-  --sysroot $($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG)/sysroot
+  --sysroot $($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG)/sysroot \
+  -B$($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG)/x86_64-linux/bin
 
 CLANG_CONFIG_x86_LINUX_HOST_EXTRA_CFLAGS := \
   --gcc-toolchain=$($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG)
diff --git a/core/clang/arm.mk b/core/clang/arm.mk
index 4053bb2..a5472f4 100644
--- a/core/clang/arm.mk
+++ b/core/clang/arm.mk
@@ -4,12 +4,6 @@
 
 CLANG_CONFIG_arm_EXTRA_CFLAGS :=
 
-ifneq (,$(filter krait,$(TARGET_$(combo_2nd_arch_prefix)CPU_VARIANT)))
-  # Android's clang support's krait as a CPU whereas GCC doesn't. Specify
-  # -mcpu here rather than the more normal core/combo/arch/arm/armv7-a-neon.mk.
-  CLANG_CONFIG_arm_EXTRA_CFLAGS += -mcpu=krait -mfpu=neon-vfpv4
-endif
-
 CLANG_CONFIG_arm_EXTRA_CPPFLAGS :=
 
 CLANG_CONFIG_arm_EXTRA_LDFLAGS :=
@@ -31,6 +25,15 @@
   -fno-tree-copy-prop \
   -fno-tree-loop-optimize
 
+ifneq (,$(filter krait,$(TARGET_$(combo_2nd_arch_prefix)CPU_VARIANT)))
+  # Android's clang support's krait as a CPU whereas GCC doesn't. Specify
+  # -mcpu here rather than the more normal core/combo/arch/arm/armv7-a-neon.mk.
+  CLANG_CONFIG_arm_EXTRA_CFLAGS += -mcpu=krait -mfpu=neon-vfpv4
+
+  # This isn't really unknown, but allows us to only set -mcpu=krait
+  CLANG_CONFIG_arm_UNKNOWN_CFLAGS += -mcpu=cortex-a15
+endif
+
 define subst-clang-incompatible-arm-flags
   $(subst -march=armv5te,-march=armv5t,\
   $(subst -march=armv5e,-march=armv5,\
diff --git a/core/clang/mips.mk b/core/clang/mips.mk
index 4a8f812..aeb2f6a 100644
--- a/core/clang/mips.mk
+++ b/core/clang/mips.mk
@@ -15,11 +15,6 @@
   -mno-synci \
   -mno-fused-madd
 
-# Temporary workaround for Mips clang++ problem,  creates
-#   relocated ptrs in read-only pic .gcc_exception_table;
-#   permanent fix pending at http://reviews.llvm.org/D9669
-CLANG_CONFIG_mips_UNKNOWN_CFLAGS += -Wl,--warn-shared-textrel
-
 # We don't have any mips flags to substitute yet.
 define subst-clang-incompatible-mips-flags
   $(1)
diff --git a/core/clang/mips64.mk b/core/clang/mips64.mk
index 1b72e05..20e87bd 100644
--- a/core/clang/mips64.mk
+++ b/core/clang/mips64.mk
@@ -15,11 +15,6 @@
   -mno-synci \
   -mno-fused-madd
 
-# Temporary workaround for Mips clang++ problem creating
-#   relocated ptrs in read-only pic .gcc_exception_table;
-#   permanent fix pending at http://reviews.llvm.org/D9669
-CLANG_CONFIG_mips64_UNKNOWN_CFLAGS += -Wl,--warn-shared-textrel
-
 # We don't have any mips64 flags to substitute yet.
 define subst-clang-incompatible-mips64-flags
   $(1)
diff --git a/core/combo/TARGET_linux-arm.mk b/core/combo/TARGET_linux-arm.mk
index d89a2cf..9be6c73 100644
--- a/core/combo/TARGET_linux-arm.mk
+++ b/core/combo/TARGET_linux-arm.mk
@@ -119,16 +119,6 @@
 			-fno-strict-volatile-bitfields
 endif
 
-# This is to avoid the dreaded warning compiler message:
-#   note: the mangling of 'va_list' has changed in GCC 4.4
-#
-# The fact that the mangling changed does not affect the NDK ABI
-# very fortunately (since none of the exposed APIs used va_list
-# in their exported C++ functions). Also, GCC 4.5 has already
-# removed the warning from the compiler.
-#
-$(combo_2nd_arch_prefix)TARGET_GLOBAL_CFLAGS += -Wno-psabi
-
 $(combo_2nd_arch_prefix)TARGET_GLOBAL_LDFLAGS += \
 			-Wl,-z,noexecstack \
 			-Wl,-z,relro \
diff --git a/core/combo/TARGET_linux-arm64.mk b/core/combo/TARGET_linux-arm64.mk
index 9ff4981..61028c4 100644
--- a/core/combo/TARGET_linux-arm64.mk
+++ b/core/combo/TARGET_linux-arm64.mk
@@ -95,16 +95,6 @@
 
 TARGET_GLOBAL_CFLAGS += -fno-strict-volatile-bitfields
 
-# This is to avoid the dreaded warning compiler message:
-#   note: the mangling of 'va_list' has changed in GCC 4.4
-#
-# The fact that the mangling changed does not affect the NDK ABI
-# very fortunately (since none of the exposed APIs used va_list
-# in their exported C++ functions). Also, GCC 4.5 has already
-# removed the warning from the compiler.
-#
-TARGET_GLOBAL_CFLAGS += -Wno-psabi
-
 TARGET_GLOBAL_LDFLAGS += \
 			-Wl,-z,noexecstack \
 			-Wl,-z,relro \
diff --git a/core/combo/arch/arm/armv7-a-neon.mk b/core/combo/arch/arm/armv7-a-neon.mk
index 5d5b050..5517a79 100644
--- a/core/combo/arch/arm/armv7-a-neon.mk
+++ b/core/combo/arch/arm/armv7-a-neon.mk
@@ -31,6 +31,11 @@
 	arch_variant_ldflags := \
 		-Wl,--no-fix-cortex-a8
 else
+ifeq ($(strip $(TARGET_$(combo_2nd_arch_prefix)CPU_VARIANT)),cortex-a9)
+	arch_variant_cflags := -march=armv7-a
+	arch_variant_ldflags := \
+		-Wl,--no-fix-cortex-a8
+else
 	arch_variant_cflags := -march=armv7-a
 	# Generic ARM might be a Cortex A8 -- better safe than sorry
 	arch_variant_ldflags := \
@@ -38,6 +43,7 @@
 endif
 endif
 endif
+endif
 
 ifeq (true,$(local_arch_has_lpae))
 	# Fake an ARM compiler flag as these processors support LPAE which GCC/clang
diff --git a/core/combo/arch/x86/x86_64.mk b/core/combo/arch/x86/x86_64.mk
new file mode 100644
index 0000000..620fbd8
--- /dev/null
+++ b/core/combo/arch/x86/x86_64.mk
@@ -0,0 +1,18 @@
+# This file is used as the second (32-bit) architecture when building a generic
+# x86_64 64-bit platform image. (full_x86_64-eng / sdk_x86_64-eng)
+#
+# The generic 'x86' variant cannot be used, since it resets some flags used
+# by the 'x86_64' variant.
+
+ARCH_X86_HAVE_SSSE3 := true
+ARCH_X86_HAVE_MOVBE := false # Only supported on Atom.
+ARCH_X86_HAVE_POPCNT := true
+ARCH_X86_HAVE_SSE4 := true
+ARCH_X86_HAVE_SSE4_1 := true
+ARCH_X86_HAVE_SSE4_2 := true
+
+
+# Some intrinsic functions used by libcxx only exist for prescott or newer CPUs.
+arch_variant_cflags := \
+    -march=prescott \
+
diff --git a/core/config.mk b/core/config.mk
index 15d8fde..516fec8 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -474,8 +474,10 @@
 MAINDEXCLASSES := $(HOST_OUT_EXECUTABLES)/mainDexClasses
 
 # Always use prebuilts for ckati and makeparallel
-CKATI := $(prebuilt_sdk_tools_bin)/ckati
-MAKEPARALLEL := $(prebuilt_sdk_tools_bin)/makeparallel
+prebuilt_build_tools := prebuilts/build-tools
+prebuilt_build_tools_bin := $(prebuilt_build_tools)/$(HOST_PREBUILT_TAG)/bin
+CKATI := $(prebuilt_build_tools_bin)/ckati
+MAKEPARALLEL := $(prebuilt_build_tools_bin)/makeparallel
 
 USE_PREBUILT_SDK_TOOLS_IN_PLACE := true
 
diff --git a/core/definitions.mk b/core/definitions.mk
index 664d86b..420691d 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -1109,6 +1109,9 @@
 	$(addprefix --proto_path=, $(PRIVATE_PROTO_INCLUDES)) \
 	$(PRIVATE_PROTOC_FLAGS) \
 	$<
+@# aprotoc outputs only .cc. Rename it to .cpp if necessary.
+$(if $(PRIVATE_RENAME_CPP_EXT),\
+  $(hide) mv $(basename $@).cc $@)
 endef
 
 
diff --git a/core/ninja.mk b/core/ninja.mk
index f5d9c89..9e78c46 100644
--- a/core/ninja.mk
+++ b/core/ninja.mk
@@ -1,4 +1,4 @@
-NINJA ?= prebuilts/ninja/$(HOST_PREBUILT_TAG)/ninja
+NINJA ?= prebuilts/build-tools/$(HOST_PREBUILT_TAG)/bin/ninja
 
 include $(BUILD_SYSTEM)/soong.mk
 
@@ -104,13 +104,15 @@
 NINJA_STATUS := [%p %s/%t]$(space)
 endif
 
+NINJA_EXTRA_ARGS :=
+
 ifneq (,$(filter showcommands,$(ORIGINAL_MAKECMDGOALS)))
-NINJA_ARGS += "-v"
+NINJA_EXTRA_ARGS += "-v"
 endif
 
 # Make multiple rules to generate the same target an error instead of
 # proceeding with undefined behavior.
-NINJA_ARGS += -w dupbuild=err
+NINJA_EXTRA_ARGS += -w dupbuild=err
 
 ifdef USE_GOMA
 KATI_MAKEPARALLEL := $(MAKEPARALLEL)
@@ -118,11 +120,13 @@
 # this parallelism. Note the parallelism of all other jobs is still
 # limited by the -j flag passed to GNU make.
 NINJA_REMOTE_NUM_JOBS ?= 500
-NINJA_ARGS += -j$(NINJA_REMOTE_NUM_JOBS)
+NINJA_EXTRA_ARGS += -j$(NINJA_REMOTE_NUM_JOBS)
 else
 NINJA_MAKEPARALLEL := $(MAKEPARALLEL) --ninja
 endif
 
+NINJA_ARGS += $(NINJA_EXTRA_ARGS)
+
 ifeq ($(USE_SOONG),true)
 COMBINED_BUILD_NINJA := $(OUT_DIR)/combined$(KATI_NINJA_SUFFIX).ninja
 
diff --git a/core/soong.mk b/core/soong.mk
index aaaa136..990a861 100644
--- a/core/soong.mk
+++ b/core/soong.mk
@@ -42,6 +42,8 @@
 	echo '    "Brillo": $(if $(BRILLO),true,false),'; \
 	echo '    "Malloc_not_svelte": $(if $(filter true,$(MALLOC_SVELTE)),false,true),'; \
 	echo '    "Allow_missing_dependencies": $(if $(TARGET_BUILD_APPS)$(filter true,$(SOONG_ALLOW_MISSING_DEPENDENCIES)),true,false),'; \
+	echo '    "SanitizeHost": [$(if $(SANITIZE_HOST),"$(subst $(comma),"$(comma)",$(SANITIZE_HOST))")],'; \
+	echo '    "SanitizeDevice": [$(if $(SANITIZE_TARGET),"$(subst $(comma),"$(comma)",$(SANITIZE_TARGET))")],'; \
 	echo ''; \
 	echo '    "DeviceName": "$(TARGET_DEVICE)",'; \
 	echo '    "DeviceArch": "$(TARGET_ARCH)",'; \
@@ -77,4 +79,4 @@
 # prebuilts.
 .PHONY: run_soong
 run_soong: $(SOONG_BOOTSTRAP) $(SOONG_VARIABLES) $(SOONG_IN_MAKE) FORCE
-	$(hide) $(SOONG) $(SOONG_BUILD_NINJA) $(NINJA_ARGS)
+	$(hide) $(SOONG) $(SOONG_BUILD_NINJA) $(NINJA_EXTRA_ARGS)
diff --git a/target/board/generic_x86_64/BoardConfig.mk b/target/board/generic_x86_64/BoardConfig.mk
index 783fc77..0946243 100755
--- a/target/board/generic_x86_64/BoardConfig.mk
+++ b/target/board/generic_x86_64/BoardConfig.mk
@@ -13,7 +13,7 @@
 
 TARGET_2ND_CPU_ABI := x86
 TARGET_2ND_ARCH := x86
-TARGET_2ND_ARCH_VARIANT := x86
+TARGET_2ND_ARCH_VARIANT := x86_64
 
 TARGET_USES_64_BIT_BINDER := true
 
diff --git a/tools/apksigner/Android.mk b/tools/apksigner/Android.mk
new file mode 100644
index 0000000..a7b4414
--- /dev/null
+++ b/tools/apksigner/Android.mk
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/apksigner/core/Android.mk b/tools/apksigner/core/Android.mk
new file mode 100644
index 0000000..c86208b
--- /dev/null
+++ b/tools/apksigner/core/Android.mk
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+
+# apksigner library, for signing APKs and verification signatures of APKs
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := apksigner-core
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES = \
+  bouncycastle-host \
+  bouncycastle-bcpkix-host
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
new file mode 100644
index 0000000..36f2a08
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
@@ -0,0 +1,407 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.SignatureException;
+import java.util.List;
+
+import com.android.apksigner.core.util.DataSink;
+import com.android.apksigner.core.util.DataSource;
+
+/**
+ * APK signing logic which is independent of how input and output APKs are stored, parsed, and
+ * generated.
+ *
+ * <p><h3>Operating Model</h3>
+ *
+ * The abstract operating model is that there is an input APK which is being signed, thus producing
+ * an output APK. In reality, there may be just an output APK being built from scratch, or the input APK and
+ * the output APK may be the same file. Because this engine does not deal with reading and writing
+ * files, it can handle all of these scenarios.
+ *
+ * <p>The engine is stateful and thus cannot be used for signing multiple APKs. However, once
+ * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified.
+ * This may be more efficient than signing the APK using a new instance of the engine. See
+ * <a href="#incremental">Incremental Operation</a>.
+ *
+ * <p>In the engine's operating model, a signed APK is produced as follows.
+ * <ol>
+ * <li>JAR entries to be signed are output,</li>
+ * <li>JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the
+ *     output,</li>
+ * <li>JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature
+ *     to the output.</li>
+ * </ol>
+ *
+ * <p>The input APK may contain JAR entries which, depending on the engine's configuration, may or
+ * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the
+ * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)}
+ * which tells the client whether the input JAR entry needs to be output. This avoids the need for
+ * the client to hard-code the aspects of APK signing which determine which parts of input must be
+ * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the
+ * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input
+ * APK.
+ *
+ * <p>To use the engine to sign an input APK (or a collection of JAR entries), follow these
+ * steps:
+ * <ol>
+ * <li>Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used
+ *     for signing multiple APKs.</li>
+ * <li>Locate the input APK's APK Signing Block and provide it to
+ *     {@link #inputApkSigningBlock(DataSource)}.</li>
+ * <li>For each JAR entry in the input APK, invoke {@link #inputJarEntry(String)} to determine
+ *     whether this entry should be output. The engine may request to inspect the entry.</li>
+ * <li>For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to
+ *     inspect the entry.</li>
+ * <li>Once all JAR entries have been output, invoke {@link #outputJarEntries()} which may request
+ *     that additional JAR entries are output. These entries comprise the output APK's JAR
+ *     signature.</li>
+ * <li>Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and
+ *     invoke {@link #outputZipSections(DataSource, DataSource, DataSource)} which may request that
+ *     an APK Signature Block is inserted before the ZIP Central Directory. The block contains the
+ *     output APK's APK Signature Scheme v2 signature.</li>
+ * <li>Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will
+ *     confirm that the output APK is signed.</li>
+ * <li>Invoke {@link #close()} to signal that the engine will no longer be used. This lets the
+ *     engine free any resources it no longer needs.
+ * </ol>
+ *
+ * <p>Some invocations of the engine may provide the client with a task to perform. The client is
+ * expected to perform all requested tasks before proceeding to the next stage of signing. See
+ * documentation of each method about the deadlines for performing the tasks requested by the
+ * method.
+ *
+ * <p><h3 id="incremental">Incremental Operation</h3></a>
+ *
+ * The engine supports incremental operation where a signed APK is produced, then modified and
+ * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes
+ * by the developer. Re-signing may be more efficient than signing from scratch.
+ *
+ * <p>To use the engine in incremental mode, keep notifying the engine of changes to the APK through
+ * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)},
+ * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)},
+ * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through
+ * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the
+ * APK.
+ *
+ * <p><h3>Output-only Operation</h3>
+ *
+ * The engine's abstract operating model consists of an input APK and an output APK. However, it is
+ * possible to use the engine in output-only mode where the engine's {@code input...} methods are
+ * not invoked. In this mode, the engine has less control over output because it cannot request that
+ * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK
+ * signed and will report an error if cannot do so.
+ */
+public interface ApkSignerEngine extends Closeable {
+
+    /**
+     * Indicates to this engine that the input APK contains the provided APK Signing Block. The
+     * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures.
+     *
+     * @param apkSigningBlock APK signing block of the input APK. The provided data source is
+     *        guaranteed to not be used by the engine after this method terminates.
+     *
+     * @throws IllegalStateException if this engine is closed
+     */
+    void inputApkSigningBlock(DataSource apkSigningBlock) throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that the specified JAR entry was encountered in the input APK.
+     *
+     * <p>When an input entry is updated/changed, it's OK to not invoke
+     * {@link #inputJarEntryRemoved(String)} before invoking this method.
+     *
+     * @return instructions about how to proceed with this entry
+     *
+     * @throws IllegalStateException if this engine is closed
+     */
+    InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that the specified JAR entry was output.
+     *
+     * <p>It is unnecessary to invoke this method for entries added to output by this engine (e.g.,
+     * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the
+     * data requested by the engine.
+     *
+     * <p>When an already output entry is updated/changed, it's OK to not invoke
+     * {@link #outputJarEntryRemoved(String)} before invoking this method.
+     *
+     * @return request to inspect the entry or {@code null} if the engine does not need to inspect
+     *         the entry. The request must be fulfilled before {@link #outputJarEntries()} is
+     *         invoked.
+     *
+     * @throws IllegalStateException if this engine is closed
+     */
+    InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that the specified JAR entry was removed from the input. It's safe
+     * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked.
+     *
+     * @return output policy of this JAR entry. The policy indicates how this input entry affects
+     *         the output APK. The client of this engine should use this information to determine
+     *         how the removal of this input APK's JAR entry affects the output APK.
+     *
+     * @throws IllegalStateException if this engine is closed
+     */
+    InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName)
+            throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that the specified JAR entry was removed from the output. It's safe
+     * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked.
+     *
+     * @throws IllegalStateException if this engine is closed
+     */
+    void outputJarEntryRemoved(String entryName) throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that all JAR entries have been output.
+     *
+     *
+     * @return request to add JAR signature to the output or {@code null} if there is no need to add
+     *         a JAR signature. The request will contain additional JAR entries to be output. The
+     *         request must be fulfilled before
+     *         {@link #outputZipSections(DataSource, DataSource, DataSource)} is invoked.
+     *
+     * @throws InvalidKeyException if a signature could not be generated because a signing key is
+     *         not suitable for generating the signature
+     * @throws SignatureException if an error occurred while generating the JAR signature
+     * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
+     *         entries, or if the engine is closed
+     */
+    OutputJarSignatureRequest outputJarEntries() throws InvalidKeyException, SignatureException;
+
+    /**
+     * Indicates to this engine that the ZIP sections comprising the output APK have been output.
+     *
+     * <p>The provided data sources are guaranteed to not be used by the engine after this method
+     * terminates.
+     *
+     * @param zipEntries the section of ZIP archive containing Local File Header records and data of
+     *        the ZIP entries. In a well-formed archive, this section starts at the start of the
+     *        archive and extends all the way to the ZIP Central Directory.
+     * @param zipCentralDirectory ZIP Central Directory section
+     * @param zipEocd ZIP End of Central Directory (EoCD) record
+     *
+     * @return request to add an APK Signing Block to the output or {@code null} if the output must
+     *         not contain an APK Signing Block. The request must be fulfilled before
+     *         {@link #outputDone()} is invoked.
+     *
+     * @throws IOException if an I/O error occurs while reading the provided ZIP sections
+     * @throws InvalidKeyException if a signature could not be generated because a signing key is
+     *         not suitable for generating the signature
+     * @throws SignatureException if an error occurred while generating the APK's signature
+     * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
+     *         entries or to output JAR signature, or if the engine is closed
+     */
+    OutputApkSigningBlockRequest outputZipSections(
+            DataSource zipEntries,
+            DataSource zipCentralDirectory,
+            DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException;
+
+    /**
+     * Indicates to this engine that the signed APK was output.
+     *
+     * <p>This does not change the output APK. The method helps the client confirm that the current
+     * output is signed.
+     *
+     * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
+     *         entries or to output signatures, or if the engine is closed
+     */
+    void outputDone() throws IllegalStateException;
+
+    /**
+     * Indicates to this engine that it will no longer be used. Invoking this on an already closed
+     * engine is OK.
+     *
+     * <p>This does not change the output APK. For example, if the output APK is not yet fully
+     * signed, it will remain so after this method terminates.
+     */
+    @Override
+    void close();
+
+    /**
+     * Instructions about how to handle an input APK's JAR entry.
+     *
+     * <p>The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and
+     * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in
+     * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is
+     * invoked.
+     */
+    public static class InputJarEntryInstructions {
+        private final OutputPolicy mOutputPolicy;
+        private final InspectJarEntryRequest mInspectJarEntryRequest;
+
+        /**
+         * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry
+         * output policy and without a request to inspect the entry.
+         */
+        public InputJarEntryInstructions(OutputPolicy outputPolicy) {
+            this(outputPolicy, null);
+        }
+
+        /**
+         * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry
+         * output mode and with the provided request to inspect the entry.
+         *
+         * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no
+         *        need to inspect the entry.
+         */
+        public InputJarEntryInstructions(
+                OutputPolicy outputPolicy,
+                InspectJarEntryRequest inspectJarEntryRequest) {
+            mOutputPolicy = outputPolicy;
+            mInspectJarEntryRequest = inspectJarEntryRequest;
+        }
+
+        /**
+         * Returns the output policy for this entry.
+         */
+        public OutputPolicy getOutputPolicy() {
+            return mOutputPolicy;
+        }
+
+        /**
+         * Returns the request to inspect the JAR entry or {@code null} if there is no need to
+         * inspect the entry.
+         */
+        public InspectJarEntryRequest getInspectJarEntryRequest() {
+            return mInspectJarEntryRequest;
+        }
+
+        /**
+         * Output policy for an input APK's JAR entry.
+         */
+        public static enum OutputPolicy {
+            /** Entry must not be output. */
+            SKIP,
+
+            /** Entry should be output. */
+            OUTPUT,
+
+            /** Entry will be output by the engine. The client can thus ignore this input entry. */
+            OUTPUT_BY_ENGINE,
+        }
+    }
+
+    /**
+     * Request to inspect the specified JAR entry.
+     *
+     * <p>The entry's uncompressed data must be provided to the data sink returned by
+     * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()}
+     * must be invoked.
+     */
+    interface InspectJarEntryRequest {
+
+        /**
+         * Returns the data sink into which the entry's uncompressed data should be sent.
+         */
+        DataSink getDataSink();
+
+        /**
+         * Indicates that entry's data has been provided in full.
+         */
+        void done();
+
+        /**
+         * Returns the name of the JAR entry.
+         */
+        String getEntryName();
+    }
+
+    /**
+     * Request to add JAR signature (aka v1 signature) to the output APK.
+     *
+     * <p>Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after
+     * which {@link #done()} must be invoked.
+     */
+    interface OutputJarSignatureRequest {
+
+        /**
+         * Returns JAR entries that must be added to the output APK.
+         */
+        List<JarEntry> getAdditionalJarEntries();
+
+        /**
+         * Indicates that the JAR entries contained in this request were added to the output APK.
+         */
+        void done();
+
+        /**
+         * JAR entry.
+         */
+        public static class JarEntry {
+            private final String mName;
+            private final byte[] mData;
+
+            /**
+             * Constructs a new {@code JarEntry} with the provided name and data.
+             *
+             * @param data uncompressed data of the entry. Changes to this array will not be
+             *        reflected in {@link #getData()}.
+             */
+            public JarEntry(String name, byte[] data) {
+                mName = name;
+                mData = data.clone();
+            }
+
+            /**
+             * Returns the name of this ZIP entry.
+             */
+            public String getName() {
+                return mName;
+            }
+
+            /**
+             * Returns the uncompressed data of this JAR entry.
+             */
+            public byte[] getData() {
+                return mData.clone();
+            }
+        }
+    }
+
+    /**
+     * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2
+     * signature(s) of the APK are contained in this block.
+     *
+     * <p>The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the
+     * output APK such that the block is immediately before the ZIP Central Directory, the offset of
+     * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted
+     * accordingly, and then {@link #done()} must be invoked.
+     *
+     * <p>If the output contains an APK Signing Block, that block must be replaced by the block
+     * contained in this request.
+     */
+    interface OutputApkSigningBlockRequest {
+
+        /**
+         * Returns the APK Signing Block.
+         */
+        byte[] getApkSigningBlock();
+
+        /**
+         * Indicates that the APK Signing Block was output as requested.
+         */
+        void done();
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java
new file mode 100644
index 0000000..71e698b
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.apk.v1;
+
+/**
+ * Digest algorithm used with JAR signing (aka v1 signing scheme).
+ */
+public enum DigestAlgorithm {
+    /** SHA-1 */
+    SHA1("SHA-1"),
+
+    /** SHA2-256 */
+    SHA256("SHA-256");
+
+    private final String mJcaMessageDigestAlgorithm;
+
+    private DigestAlgorithm(String jcaMessageDigestAlgoritm) {
+        mJcaMessageDigestAlgorithm = jcaMessageDigestAlgoritm;
+    }
+
+    /**
+     * Returns the {@link java.security.MessageDigest} algorithm represented by this digest
+     * algorithm.
+     */
+    String getJcaMessageDigestAlgorithm() {
+        return mJcaMessageDigestAlgorithm;
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
new file mode 100644
index 0000000..b99cdec
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
@@ -0,0 +1,526 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.apk.v1;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+import com.android.apksigner.core.internal.jar.ManifestWriter;
+import com.android.apksigner.core.internal.jar.SignatureFileWriter;
+import com.android.apksigner.core.internal.util.Pair;
+
+/**
+ * APK signer which uses JAR signing (aka v1 signing scheme).
+ *
+ * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a>
+ */
+public abstract class V1SchemeSigner {
+
+    public static final String MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF";
+
+    private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY =
+            new Attributes.Name("Created-By");
+    private static final String ATTRIBUTE_DEFALT_VALUE_CREATED_BY = "1.0 (Android apksigner)";
+    private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0";
+    private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0";
+
+    private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME =
+            new Attributes.Name("X-Android-APK-Signed");
+
+    /**
+     * Signer configuration.
+     */
+    public static class SignerConfig {
+        /** Name. */
+        public String name;
+
+        /** Private key. */
+        public PrivateKey privateKey;
+
+        /**
+         * Certificates, with the first certificate containing the public key corresponding to
+         * {@link #privateKey}.
+         */
+        public List<X509Certificate> certificates;
+
+        /**
+         * Digest algorithm used for the signature.
+         */
+        public DigestAlgorithm signatureDigestAlgorithm;
+
+        /**
+         * Digest algorithm used for digests of JAR entries and MANIFEST.MF.
+         */
+        public DigestAlgorithm contentDigestAlgorithm;
+    }
+
+    /** Hidden constructor to prevent instantiation. */
+    private V1SchemeSigner() {}
+
+    /**
+     * Gets the JAR signing digest algorithm to be used for signing an APK using the provided key.
+     *
+     * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
+     *        AndroidManifest.xml minSdkVersion attribute)
+     *
+     * @throws InvalidKeyException if the provided key is not suitable for signing APKs using
+     *         JAR signing (aka v1 signature scheme)
+     */
+    public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm(
+            PublicKey signingKey, int minSdkVersion) throws InvalidKeyException {
+        String keyAlgorithm = signingKey.getAlgorithm();
+        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+            // Prior to API Level 18, only SHA-1 can be used with RSA.
+            if (minSdkVersion < 18) {
+                return DigestAlgorithm.SHA1;
+            }
+            return DigestAlgorithm.SHA256;
+        } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+            // Prior to API Level 21, only SHA-1 can be used with DSA
+            if (minSdkVersion < 21) {
+                return DigestAlgorithm.SHA1;
+            } else {
+                return DigestAlgorithm.SHA256;
+            }
+        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+            if (minSdkVersion < 18) {
+                throw new InvalidKeyException(
+                        "ECDSA signatures only supported for minSdkVersion 18 and higher");
+            }
+            // Prior to API Level 21, only SHA-1 can be used with ECDSA
+            if (minSdkVersion < 21) {
+                return DigestAlgorithm.SHA1;
+            } else {
+                return DigestAlgorithm.SHA256;
+            }
+        } else {
+            throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    /**
+     * Returns the JAR signing digest algorithm to be used for JAR entry digests.
+     *
+     * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
+     *        AndroidManifest.xml minSdkVersion attribute)
+     */
+    public static DigestAlgorithm getSuggestedContentDigestAlgorithm(int minSdkVersion) {
+        return (minSdkVersion >= 18) ? DigestAlgorithm.SHA256 : DigestAlgorithm.SHA1;
+    }
+
+    /**
+     * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm.
+     */
+    public static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm) {
+        String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
+        try {
+            return MessageDigest.getInstance(jcaAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Failed to obtain " + jcaAlgorithm + " MessageDigest", e);
+        }
+    }
+
+    /**
+     * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's
+     * manifest.
+     */
+    public static boolean isJarEntryDigestNeededInManifest(String entryName) {
+        // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File
+
+        // Entries outside of META-INF must be listed in the manifest.
+        if (!entryName.startsWith("META-INF/")) {
+            return true;
+        }
+        // Entries in subdirectories of META-INF must be listed in the manifest.
+        if (entryName.indexOf('/', "META-INF/".length()) != -1) {
+            return true;
+        }
+
+        // Ignored file names (case-insensitive) in META-INF directory:
+        //   MANIFEST.MF
+        //   *.SF
+        //   *.RSA
+        //   *.DSA
+        //   *.EC
+        //   SIG-*
+        String fileNameLowerCase =
+                entryName.substring("META-INF/".length()).toLowerCase(Locale.US);
+        if (("manifest.mf".equals(fileNameLowerCase))
+                || (fileNameLowerCase.endsWith(".sf"))
+                || (fileNameLowerCase.endsWith(".rsa"))
+                || (fileNameLowerCase.endsWith(".dsa"))
+                || (fileNameLowerCase.endsWith(".ec"))
+                || (fileNameLowerCase.startsWith("sig-"))) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of
+     * JAR entries which need to be added to the APK as part of the signature.
+     *
+     * @param signerConfigs signer configurations, one for each signer. At least one signer config
+     *        must be provided.
+     *
+     * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
+     *         cannot be used in general
+     * @throws SignatureException if an error occurs when computing digests of generating
+     *         signatures
+     */
+    public static List<Pair<String, byte[]>> sign(
+            List<SignerConfig> signerConfigs,
+            DigestAlgorithm jarEntryDigestAlgorithm,
+            Map<String, byte[]> jarEntryDigests,
+            List<Integer> apkSigningSchemeIds,
+            byte[] sourceManifestBytes)
+                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
+        if (signerConfigs.isEmpty()) {
+            throw new IllegalArgumentException("At least one signer config must be provided");
+        }
+        OutputManifestFile manifest =
+                generateManifestFile(jarEntryDigestAlgorithm, jarEntryDigests, sourceManifestBytes);
+
+        return signManifest(signerConfigs, jarEntryDigestAlgorithm, apkSigningSchemeIds, manifest);
+    }
+
+    /**
+     * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of
+     * JAR entries which need to be added to the APK as part of the signature.
+     *
+     * @param signerConfigs signer configurations, one for each signer. At least one signer config
+     *        must be provided.
+     *
+     * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
+     *         cannot be used in general
+     * @throws SignatureException if an error occurs when computing digests of generating
+     *         signatures
+     */
+    public static List<Pair<String, byte[]>> signManifest(
+            List<SignerConfig> signerConfigs,
+            DigestAlgorithm digestAlgorithm,
+            List<Integer> apkSigningSchemeIds,
+            OutputManifestFile manifest)
+                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
+        if (signerConfigs.isEmpty()) {
+            throw new IllegalArgumentException("At least one signer config must be provided");
+        }
+
+        // For each signer output .SF and .(RSA|DSA|EC) file, then output MANIFEST.MF.
+        List<Pair<String, byte[]>> signatureJarEntries =
+                new ArrayList<>(2 * signerConfigs.size() + 1);
+        byte[] sfBytes =
+                generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, manifest);
+        for (SignerConfig signerConfig : signerConfigs) {
+            String signerName = signerConfig.name;
+            byte[] signatureBlock;
+            try {
+                signatureBlock = generateSignatureBlock(signerConfig, sfBytes);
+            } catch (InvalidKeyException e) {
+                throw new InvalidKeyException(
+                        "Failed to sign using signer \"" + signerName + "\"", e);
+            } catch (CertificateEncodingException e) {
+                throw new CertificateEncodingException(
+                        "Failed to sign using signer \"" + signerName + "\"", e);
+            } catch (SignatureException e) {
+                throw new SignatureException(
+                        "Failed to sign using signer \"" + signerName + "\"", e);
+            }
+            signatureJarEntries.add(Pair.of("META-INF/" + signerName + ".SF", sfBytes));
+            PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
+            String signatureBlockFileName =
+                    "META-INF/" + signerName + "."
+                            + publicKey.getAlgorithm().toUpperCase(Locale.US);
+            signatureJarEntries.add(
+                    Pair.of(signatureBlockFileName, signatureBlock));
+        }
+        signatureJarEntries.add(Pair.of(MANIFEST_ENTRY_NAME, manifest.contents));
+        return signatureJarEntries;
+    }
+
+    /**
+     * Returns the names of JAR entries which this signer will produce as part of v1 signature.
+     */
+    public static Set<String> getOutputEntryNames(List<SignerConfig> signerConfigs) {
+        Set<String> result = new HashSet<>(2 * signerConfigs.size() + 1);
+        for (SignerConfig signerConfig : signerConfigs) {
+            String signerName = signerConfig.name;
+            result.add("META-INF/" + signerName + ".SF");
+            PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
+            String signatureBlockFileName =
+                    "META-INF/" + signerName + "."
+                            + publicKey.getAlgorithm().toUpperCase(Locale.US);
+            result.add(signatureBlockFileName);
+        }
+        result.add(MANIFEST_ENTRY_NAME);
+        return result;
+    }
+
+    /**
+     * Generated and returns the {@code META-INF/MANIFEST.MF} file based on the provided (optional)
+     * input {@code MANIFEST.MF} and digests of JAR entries covered by the manifest.
+     */
+    public static OutputManifestFile generateManifestFile(
+            DigestAlgorithm jarEntryDigestAlgorithm,
+            Map<String, byte[]> jarEntryDigests,
+            byte[] sourceManifestBytes) {
+        Manifest sourceManifest = null;
+        if (sourceManifestBytes != null) {
+            try {
+                sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes));
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Failed to parse source MANIFEST.MF", e);
+            }
+        }
+        ByteArrayOutputStream manifestOut = new ByteArrayOutputStream();
+        Attributes mainAttrs = new Attributes();
+        // Copy the main section from the source manifest (if provided). Otherwise use defaults.
+        if (sourceManifest != null) {
+            mainAttrs.putAll(sourceManifest.getMainAttributes());
+        } else {
+            mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION);
+            mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY);
+        }
+
+        try {
+            ManifestWriter.writeMainSection(manifestOut, mainAttrs);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e);
+        }
+
+        List<String> sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet());
+        Collections.sort(sortedEntryNames);
+        SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>();
+        String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm);
+        for (String entryName : sortedEntryNames) {
+            byte[] entryDigest = jarEntryDigests.get(entryName);
+            Attributes entryAttrs = new Attributes();
+            entryAttrs.putValue(
+                    entryDigestAttributeName,
+                    Base64.getEncoder().encodeToString(entryDigest));
+            ByteArrayOutputStream sectionOut = new ByteArrayOutputStream();
+            byte[] sectionBytes;
+            try {
+                ManifestWriter.writeIndividualSection(sectionOut, entryName, entryAttrs);
+                sectionBytes = sectionOut.toByteArray();
+                manifestOut.write(sectionBytes);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e);
+            }
+            invidualSectionsContents.put(entryName, sectionBytes);
+        }
+
+        OutputManifestFile result = new OutputManifestFile();
+        result.contents = manifestOut.toByteArray();
+        result.mainSectionAttributes = mainAttrs;
+        result.individualSectionsContents = invidualSectionsContents;
+        return result;
+    }
+
+    public static class OutputManifestFile {
+        public byte[] contents;
+        public SortedMap<String, byte[]> individualSectionsContents;
+        public Attributes mainSectionAttributes;
+    }
+
+    private static byte[] generateSignatureFile(
+            List<Integer> apkSignatureSchemeIds,
+            DigestAlgorithm manifestDigestAlgorithm,
+            OutputManifestFile manifest) {
+        Manifest sf = new Manifest();
+        Attributes mainAttrs = sf.getMainAttributes();
+        mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION);
+        mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY);
+        if (!apkSignatureSchemeIds.isEmpty()) {
+            // Add APK Signature Scheme v2 (and newer) signature stripping protection.
+            // This attribute indicates that this APK is supposed to have been signed using one or
+            // more APK-specific signature schemes in addition to the standard JAR signature scheme
+            // used by this code. APK signature verifier should reject the APK if it does not
+            // contain a signature for the signature scheme the verifier prefers out of this set.
+            StringBuilder attrValue = new StringBuilder();
+            for (int id : apkSignatureSchemeIds) {
+                if (attrValue.length() > 0) {
+                    attrValue.append(", ");
+                }
+                attrValue.append(String.valueOf(id));
+            }
+            mainAttrs.put(
+                    SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME,
+                    attrValue.toString());
+        }
+
+        // Add main attribute containing the digest of MANIFEST.MF.
+        MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm);
+        mainAttrs.putValue(
+                getManifestDigestAttributeName(manifestDigestAlgorithm),
+                Base64.getEncoder().encodeToString(md.digest(manifest.contents)));
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            SignatureFileWriter.writeMainSection(out, mainAttrs);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to write in-memory .SF file", e);
+        }
+        String entryDigestAttributeName = getEntryDigestAttributeName(manifestDigestAlgorithm);
+        for (Map.Entry<String, byte[]> manifestSection
+                : manifest.individualSectionsContents.entrySet()) {
+            String sectionName = manifestSection.getKey();
+            byte[] sectionContents = manifestSection.getValue();
+            byte[] sectionDigest = md.digest(sectionContents);
+            Attributes attrs = new Attributes();
+            attrs.putValue(
+                    entryDigestAttributeName,
+                    Base64.getEncoder().encodeToString(sectionDigest));
+
+            try {
+                SignatureFileWriter.writeIndividualSection(out, sectionName, attrs);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to write in-memory .SF file", e);
+            }
+        }
+
+        // A bug in the java.util.jar implementation of Android platforms up to version 1.6 will
+        // cause a spurious IOException to be thrown if the length of the signature file is a
+        // multiple of 1024 bytes. As a workaround, add an extra CRLF in this case.
+        if ((out.size() > 0) && ((out.size() % 1024) == 0)) {
+            try {
+                SignatureFileWriter.writeSectionDelimiter(out);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to write to ByteArrayOutputStream", e);
+            }
+        }
+
+        return out.toByteArray();
+    }
+
+    private static byte[] generateSignatureBlock(
+            SignerConfig signerConfig, byte[] signatureFileBytes)
+                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
+        JcaCertStore certs = new JcaCertStore(signerConfig.certificates);
+        X509Certificate signerCert = signerConfig.certificates.get(0);
+        String jcaSignatureAlgorithm =
+                getJcaSignatureAlgorithm(
+                        signerCert.getPublicKey(), signerConfig.signatureDigestAlgorithm);
+        try {
+            ContentSigner signer =
+                    new JcaContentSignerBuilder(jcaSignatureAlgorithm)
+                    .build(signerConfig.privateKey);
+            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+            gen.addSignerInfoGenerator(
+                    new JcaSignerInfoGeneratorBuilder(
+                            new JcaDigestCalculatorProviderBuilder().build())
+                    .setDirectSignature(true)
+                    .build(signer, signerCert));
+            gen.addCertificates(certs);
+
+            CMSSignedData sigData =
+                    gen.generate(new CMSProcessableByteArray(signatureFileBytes), false);
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {
+                DEROutputStream dos = new DEROutputStream(out);
+                dos.writeObject(asn1.readObject());
+            }
+            return out.toByteArray();
+        } catch (OperatorCreationException | CMSException | IOException e) {
+            throw new SignatureException("Failed to generate signature", e);
+        }
+    }
+
+    private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case SHA1:
+                return "SHA1-Digest";
+            case SHA256:
+                return "SHA-256-Digest";
+            default:
+                throw new IllegalArgumentException(
+                        "Unexpected content digest algorithm: " + digestAlgorithm);
+        }
+    }
+
+    private static String getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case SHA1:
+                return "SHA1-Digest-Manifest";
+            case SHA256:
+                return "SHA-256-Digest-Manifest";
+            default:
+                throw new IllegalArgumentException(
+                        "Unexpected content digest algorithm: " + digestAlgorithm);
+        }
+    }
+
+    private static String getJcaSignatureAlgorithm(
+            PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
+        String keyAlgorithm = publicKey.getAlgorithm();
+        String digestPrefixForSigAlg;
+        switch (digestAlgorithm) {
+            case SHA1:
+                digestPrefixForSigAlg = "SHA1";
+                break;
+            case SHA256:
+                digestPrefixForSigAlg = "SHA256";
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Unexpected digest algorithm: " + digestAlgorithm);
+        }
+        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+            return digestPrefixForSigAlg + "withRSA";
+        } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+            return digestPrefixForSigAlg + "withDSA";
+        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+            return digestPrefixForSigAlg + "withECDSA";
+        } else {
+            throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
new file mode 100644
index 0000000..cb0f84a
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.apk.v2;
+
+/**
+ * APK Signature Scheme v2 content digest algorithm.
+ */
+enum ContentDigestAlgorithm {
+    /** SHA2-256 over 1 MB chunks. */
+    CHUNKED_SHA256("SHA-256", 256 / 8),
+
+    /** SHA2-512 over 1 MB chunks. */
+    CHUNKED_SHA512("SHA-512", 512 / 8);
+
+    private final String mJcaMessageDigestAlgorithm;
+    private final int mChunkDigestOutputSizeBytes;
+
+    private ContentDigestAlgorithm(
+            String jcaMessageDigestAlgorithm, int chunkDigestOutputSizeBytes) {
+        mJcaMessageDigestAlgorithm = jcaMessageDigestAlgorithm;
+        mChunkDigestOutputSizeBytes = chunkDigestOutputSizeBytes;
+    }
+
+    /**
+     * Returns the {@link java.security.MessageDigest} algorithm used for computing digests of
+     * chunks by this content digest algorithm.
+     */
+    String getJcaMessageDigestAlgorithm() {
+        return mJcaMessageDigestAlgorithm;
+    }
+
+    /**
+     * Returns the size (in bytes) of the digest of a chunk of content.
+     */
+    int getChunkDigestOutputSizeBytes() {
+        return mChunkDigestOutputSizeBytes;
+    }
+}
\ No newline at end of file
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
new file mode 100644
index 0000000..182b4ed
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+package com.android.apksigner.core.internal.apk.v2;
+
+import com.android.apksigner.core.util.DataSink;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+
+/**
+ * Data sink which feeds all received data into the associated {@link MessageDigest} instances. Each
+ * {@code MessageDigest} instance receives the same data.
+ */
+class MessageDigestSink implements DataSink {
+
+    private final MessageDigest[] mMessageDigests;
+
+    MessageDigestSink(MessageDigest[] digests) {
+        mMessageDigests = digests;
+    }
+
+    @Override
+    public void consume(byte[] buf, int offset, int length) {
+        for (MessageDigest md : mMessageDigests) {
+            md.update(buf, offset, length);
+        }
+    }
+
+    @Override
+    public void consume(ByteBuffer buf) {
+        int originalPosition = buf.position();
+        for (MessageDigest md : mMessageDigests) {
+            // Reset the position back to the original because the previous iteration's
+            // MessageDigest.update set the buffer's position to the buffer's limit.
+            buf.position(originalPosition);
+            md.update(buf);
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
new file mode 100644
index 0000000..3c7b5f0
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.apk.v2;
+
+import com.android.apksigner.core.internal.util.Pair;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+
+/**
+ * APK Signature Scheme v2 content digest algorithm.
+ */
+public enum SignatureAlgorithm {
+    /**
+     * RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc, content
+     * digested using SHA2-256 in 1 MB chunks.
+     */
+    RSA_PSS_WITH_SHA256(
+            0x0101,
+            ContentDigestAlgorithm.CHUNKED_SHA256,
+            "RSA",
+            Pair.of("SHA256withRSA/PSS",
+                    new PSSParameterSpec(
+                            "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1))),
+
+    /**
+     * RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc, content
+     * digested using SHA2-512 in 1 MB chunks.
+     */
+    RSA_PSS_WITH_SHA512(
+            0x0102,
+            ContentDigestAlgorithm.CHUNKED_SHA512,
+            "RSA",
+            Pair.of(
+                    "SHA512withRSA/PSS",
+                    new PSSParameterSpec(
+                            "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1))),
+
+    /** RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
+    RSA_PKCS1_V1_5_WITH_SHA256(
+            0x0103,
+            ContentDigestAlgorithm.CHUNKED_SHA256,
+            "RSA",
+            Pair.of("SHA256withRSA", null)),
+
+    /** RSASSA-PKCS1-v1_5 with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */
+    RSA_PKCS1_V1_5_WITH_SHA512(
+            0x0104,
+            ContentDigestAlgorithm.CHUNKED_SHA512,
+            "RSA",
+            Pair.of("SHA512withRSA", null)),
+
+    /** ECDSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
+    ECDSA_WITH_SHA256(
+            0x0201,
+            ContentDigestAlgorithm.CHUNKED_SHA256,
+            "EC",
+            Pair.of("SHA256withECDSA", null)),
+
+    /** ECDSA with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */
+    ECDSA_WITH_SHA512(
+            0x0202,
+            ContentDigestAlgorithm.CHUNKED_SHA512,
+            "EC",
+            Pair.of("SHA512withECDSA", null)),
+
+    /** DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
+    DSA_WITH_SHA256(
+            0x0301,
+            ContentDigestAlgorithm.CHUNKED_SHA256,
+            "DSA",
+            Pair.of("SHA256withDSA", null));
+
+    private final int mId;
+    private final String mJcaKeyAlgorithm;
+    private final ContentDigestAlgorithm mContentDigestAlgorithm;
+    private final Pair<String, ? extends AlgorithmParameterSpec> mJcaSignatureAlgAndParams;
+
+    private SignatureAlgorithm(int id,
+            ContentDigestAlgorithm contentDigestAlgorithm,
+            String jcaKeyAlgorithm,
+            Pair<String, ? extends AlgorithmParameterSpec> jcaSignatureAlgAndParams) {
+        mId = id;
+        mContentDigestAlgorithm = contentDigestAlgorithm;
+        mJcaKeyAlgorithm = jcaKeyAlgorithm;
+        mJcaSignatureAlgAndParams = jcaSignatureAlgAndParams;
+    }
+
+    /**
+     * Returns the ID of this signature algorithm as used in APK Signature Scheme v2 wire format.
+     */
+    int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the content digest algorithm associated with this signature algorithm.
+     */
+    ContentDigestAlgorithm getContentDigestAlgorithm() {
+        return mContentDigestAlgorithm;
+    }
+
+    /**
+     * Returns the JCA {@link java.security.Key} algorithm used by this signature scheme.
+     */
+    String getJcaKeyAlgorithm() {
+        return mJcaKeyAlgorithm;
+    }
+
+    /**
+     * Returns the {@link java.security.Signature} algorithm and the {@link AlgorithmParameterSpec}
+     * (or null if not needed) to parameterize the {@code Signature}.
+     */
+    Pair<String, ? extends AlgorithmParameterSpec> getJcaSignatureAlgorithmAndParams() {
+        return mJcaSignatureAlgAndParams;
+    }
+
+    static SignatureAlgorithm findById(int id) {
+        for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+            if (alg.getId() == id) {
+                return alg;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
new file mode 100644
index 0000000..e185346
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
@@ -0,0 +1,614 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.apk.v2;
+
+import com.android.apksigner.core.internal.util.ByteBufferSink;
+import com.android.apksigner.core.internal.util.Pair;
+import com.android.apksigner.core.internal.zip.ZipUtils;
+import com.android.apksigner.core.util.DataSource;
+import com.android.apksigner.core.util.DataSources;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * APK Signature Scheme v2 signer.
+ *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * <p>TODO: Link to APK Signature Scheme v2 documentation once it's available.
+ */
+public abstract class V2SchemeSigner {
+    /*
+     * The two main goals of APK Signature Scheme v2 are:
+     * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature
+     *    cover every byte of the APK being signed.
+     * 2. Enable much faster signature and integrity verification. This is achieved by requiring
+     *    only a minimal amount of APK parsing before the signature is verified, thus completely
+     *    bypassing ZIP entry decompression and by making integrity verification parallelizable by
+     *    employing a hash tree.
+     *
+     * The generated signature block is wrapped into an APK Signing Block and inserted into the
+     * original APK immediately before the start of ZIP Central Directory. This is to ensure that
+     * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for
+     * extensibility. For example, a future signature scheme could insert its signatures there as
+     * well. The contract of the APK Signing Block is that all contents outside of the block must be
+     * protected by signatures inside the block.
+     */
+
+    private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
+
+    private static final byte[] APK_SIGNING_BLOCK_MAGIC =
+          new byte[] {
+              0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
+              0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
+          };
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
+    /**
+     * Signer configuration.
+     */
+    public static class SignerConfig {
+        /** Private key. */
+        public PrivateKey privateKey;
+
+        /**
+         * Certificates, with the first certificate containing the public key corresponding to
+         * {@link #privateKey}.
+         */
+        public List<X509Certificate> certificates;
+
+        /**
+         * List of signature algorithms with which to sign.
+         */
+        public List<SignatureAlgorithm> signatureAlgorithms;
+    }
+
+    /** Hidden constructor to prevent instantiation. */
+    private V2SchemeSigner() {}
+
+    /**
+     * Gets the APK Signature Scheme v2 signature algorithms to be used for signing an APK using the
+     * provided key.
+     *
+     * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
+     *        AndroidManifest.xml minSdkVersion attribute).
+     *
+     * @throws InvalidKeyException if the provided key is not suitable for signing APKs using
+     *         APK Signature Scheme v2
+     */
+    public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(
+            PublicKey signingKey, int minSdkVersion) throws InvalidKeyException {
+        String keyAlgorithm = signingKey.getAlgorithm();
+        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+            // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
+            // deterministic signatures which make life easier for OTA updates (fewer files
+            // changed when deterministic signature schemes are used).
+
+            // Pick a digest which is no weaker than the key.
+            int modulusLengthBits = ((RSAKey) signingKey).getModulus().bitLength();
+            if (modulusLengthBits <= 3072) {
+                // 3072-bit RSA is roughly 128-bit strong, meaning SHA-256 is a good fit.
+                return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256);
+            } else {
+                // Keys longer than 3072 bit need to be paired with a stronger digest to avoid the
+                // digest being the weak link. SHA-512 is the next strongest supported digest.
+                return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA512);
+            }
+        } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+            // DSA is supported only with SHA-256.
+            return Collections.singletonList(SignatureAlgorithm.DSA_WITH_SHA256);
+        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+            // Pick a digest which is no weaker than the key.
+            int keySizeBits = ((ECKey) signingKey).getParams().getOrder().bitLength();
+            if (keySizeBits <= 256) {
+                // 256-bit Elliptic Curve is roughly 128-bit strong, meaning SHA-256 is a good fit.
+                return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA256);
+            } else {
+                // Keys longer than 256 bit need to be paired with a stronger digest to avoid the
+                // digest being the weak link. SHA-512 is the next strongest supported digest.
+                return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA512);
+            }
+        } else {
+            throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    /**
+     * Signs the provided APK using APK Signature Scheme v2 and returns the APK Signing Block
+     * containing the signature.
+     *
+     * @param signerConfigs signer configurations, one for each signer At least one signer config
+     *        must be provided.
+     *
+     * @throws IOException if an I/O error occurs
+     * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
+     *         cannot be used in general
+     * @throws SignatureException if an error occurs when computing digests of generating
+     *         signatures
+     */
+    public static byte[] generateApkSigningBlock(
+            DataSource beforeCentralDir,
+            DataSource centralDir,
+            DataSource eocd,
+            List<SignerConfig> signerConfigs)
+                        throws IOException, InvalidKeyException, SignatureException {
+        if (signerConfigs.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "No signer configs provided. At least one is required");
+        }
+
+        // Figure out which digest(s) to use for APK contents.
+        Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<>(1);
+        for (SignerConfig signerConfig : signerConfigs) {
+            for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
+                contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
+            }
+        }
+
+        // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory
+        // offset field is treated as pointing to the offset at which the APK Signing Block will
+        // start.
+        long centralDirOffsetForDigesting = beforeCentralDir.size();
+        ByteBuffer eocdBuf = copyToByteBuffer(eocd);
+        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting);
+
+        // Compute digests of APK contents.
+        Map<ContentDigestAlgorithm, byte[]> contentDigests; // digest algorithm ID -> digest
+        try {
+            contentDigests =
+                    computeContentDigests(
+                            contentDigestAlgorithms,
+                            new DataSource[] {
+                                    beforeCentralDir,
+                                    centralDir,
+                                    DataSources.asDataSource(eocdBuf)});
+        } catch (IOException e) {
+            throw new IOException("Failed to read APK being signed", e);
+        } catch (DigestException e) {
+            throw new SignatureException("Failed to compute digests of APK", e);
+        }
+
+        // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
+        return generateApkSigningBlock(signerConfigs, contentDigests);
+    }
+
+    private static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
+            Set<ContentDigestAlgorithm> digestAlgorithms,
+            DataSource[] contents) throws IOException, DigestException {
+        // For each digest algorithm the result is computed as follows:
+        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+        //    No chunks are produced for empty (zero length) segments.
+        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+        //    length in bytes (uint32 little-endian) and the chunk's contents.
+        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+        //    segments in-order.
+
+        long chunkCountLong = 0;
+        for (DataSource input : contents) {
+            chunkCountLong +=
+                    getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
+        }
+        if (chunkCountLong > Integer.MAX_VALUE) {
+            throw new DigestException("Input too long: " + chunkCountLong + " chunks");
+        }
+        int chunkCount = (int) chunkCountLong;
+
+        ContentDigestAlgorithm[] digestAlgorithmsArray =
+                digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]);
+        MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length];
+        byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][];
+        int[] digestOutputSizes = new int[digestAlgorithmsArray.length];
+        for (int i = 0; i < digestAlgorithmsArray.length; i++) {
+            ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
+            int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes();
+            digestOutputSizes[i] = digestOutputSizeBytes;
+            byte[] concatenationOfChunkCountAndChunkDigests =
+                    new byte[5 + chunkCount * digestOutputSizeBytes];
+            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+            setUnsignedInt32LittleEndian(
+                    chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
+            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+            String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
+            try {
+                mds[i] = MessageDigest.getInstance(jcaAlgorithm);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithm + " MessageDigest not supported", e);
+            }
+        }
+
+        MessageDigestSink mdSink = new MessageDigestSink(mds);
+        byte[] chunkContentPrefix = new byte[5];
+        chunkContentPrefix[0] = (byte) 0xa5;
+        int chunkIndex = 0;
+        // Optimization opportunity: digests of chunks can be computed in parallel. However,
+        // determining the number of computations to be performed in parallel is non-trivial. This
+        // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched
+        // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU
+        // cores, load on the system from other threads of execution and other processes, size of
+        // input.
+        // For now, we compute these digests sequentially and thus have the luxury of improving
+        // performance by writing the digest of each chunk into a pre-allocated buffer at exactly
+        // the right position. This avoids unnecessary allocations, copying, and enables the final
+        // digest to be more efficient because it's presented with all of its input in one go.
+        for (DataSource input : contents) {
+            long inputOffset = 0;
+            long inputRemaining = input.size();
+            while (inputRemaining > 0) {
+                int chunkSize =
+                        (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
+                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+                for (int i = 0; i < mds.length; i++) {
+                    mds[i].update(chunkContentPrefix);
+                }
+                try {
+                    input.feed(inputOffset, chunkSize, mdSink);
+                } catch (IOException e) {
+                    throw new IOException("Failed to read chunk #" + chunkIndex, e);
+                }
+                for (int i = 0; i < digestAlgorithmsArray.length; i++) {
+                    MessageDigest md = mds[i];
+                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+                    int expectedDigestSizeBytes = digestOutputSizes[i];
+                    int actualDigestSizeBytes =
+                            md.digest(
+                                    concatenationOfChunkCountAndChunkDigests,
+                                    5 + chunkIndex * expectedDigestSizeBytes,
+                                    expectedDigestSizeBytes);
+                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+                        throw new RuntimeException(
+                                "Unexpected output size of " + md.getAlgorithm()
+                                        + " digest: " + actualDigestSizeBytes);
+                    }
+                }
+                inputOffset += chunkSize;
+                inputRemaining -= chunkSize;
+                chunkIndex++;
+            }
+        }
+
+        Map<ContentDigestAlgorithm, byte[]> result = new HashMap<>(digestAlgorithmsArray.length);
+        for (int i = 0; i < digestAlgorithmsArray.length; i++) {
+            ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
+            byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+            MessageDigest md = mds[i];
+            byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests);
+            result.put(digestAlgorithm, digest);
+        }
+        return result;
+    }
+
+    private static final long getChunkCount(long inputSize, int chunkSize) {
+        return (inputSize + chunkSize - 1) / chunkSize;
+    }
+
+    private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+        result[offset] = (byte) (value & 0xff);
+        result[offset + 1] = (byte) ((value >> 8) & 0xff);
+        result[offset + 2] = (byte) ((value >> 16) & 0xff);
+        result[offset + 3] = (byte) ((value >> 24) & 0xff);
+    }
+
+    private static byte[] generateApkSigningBlock(
+            List<SignerConfig> signerConfigs,
+            Map<ContentDigestAlgorithm, byte[]> contentDigests)
+                    throws InvalidKeyException, SignatureException {
+        byte[] apkSignatureSchemeV2Block =
+                generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
+        return generateApkSigningBlock(apkSignatureSchemeV2Block);
+    }
+
+    private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
+        // FORMAT:
+        // uint64:  size (excluding this field)
+        // repeated ID-value pairs:
+        //     uint64:           size (excluding this field)
+        //     uint32:           ID
+        //     (size - 4) bytes: value
+        // uint64:  size (same as the one above)
+        // uint128: magic
+
+        int resultSize =
+                8 // size
+                + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
+                + 8 // size
+                + 16 // magic
+                ;
+        ByteBuffer result = ByteBuffer.allocate(resultSize);
+        result.order(ByteOrder.LITTLE_ENDIAN);
+        long blockSizeFieldValue = resultSize - 8;
+        result.putLong(blockSizeFieldValue);
+
+        long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
+        result.putLong(pairSizeFieldValue);
+        result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
+        result.put(apkSignatureSchemeV2Block);
+
+        result.putLong(blockSizeFieldValue);
+        result.put(APK_SIGNING_BLOCK_MAGIC);
+
+        return result.array();
+    }
+
+    private static byte[] generateApkSignatureSchemeV2Block(
+            List<SignerConfig> signerConfigs,
+            Map<ContentDigestAlgorithm, byte[]> contentDigests)
+                    throws InvalidKeyException, SignatureException {
+        // FORMAT:
+        // * length-prefixed sequence of length-prefixed signer blocks.
+
+        List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());
+        int signerNumber = 0;
+        for (SignerConfig signerConfig : signerConfigs) {
+            signerNumber++;
+            byte[] signerBlock;
+            try {
+                signerBlock = generateSignerBlock(signerConfig, contentDigests);
+            } catch (InvalidKeyException e) {
+                throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
+            } catch (SignatureException e) {
+                throw new SignatureException("Signer #" + signerNumber + " failed", e);
+            }
+            signerBlocks.add(signerBlock);
+        }
+
+        return encodeAsSequenceOfLengthPrefixedElements(
+                new byte[][] {
+                    encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
+                });
+    }
+
+    private static byte[] generateSignerBlock(
+            SignerConfig signerConfig,
+            Map<ContentDigestAlgorithm, byte[]> contentDigests)
+                    throws InvalidKeyException, SignatureException {
+        if (signerConfig.certificates.isEmpty()) {
+            throw new SignatureException("No certificates configured for signer");
+        }
+        PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
+
+        byte[] encodedPublicKey = encodePublicKey(publicKey);
+
+        V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
+        try {
+            signedData.certificates = encodeCertificates(signerConfig.certificates);
+        } catch (CertificateEncodingException e) {
+            throw new SignatureException("Failed to encode certificates", e);
+        }
+
+        List<Pair<Integer, byte[]>> digests =
+                new ArrayList<>(signerConfig.signatureAlgorithms.size());
+        for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
+            ContentDigestAlgorithm contentDigestAlgorithm =
+                    signatureAlgorithm.getContentDigestAlgorithm();
+            byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
+            if (contentDigest == null) {
+                throw new RuntimeException(
+                        contentDigestAlgorithm + " content digest for " + signatureAlgorithm
+                                + " not computed");
+            }
+            digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest));
+        }
+        signedData.digests = digests;
+
+        V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();
+        // FORMAT:
+        // * length-prefixed sequence of length-prefixed digests:
+        //   * uint32: signature algorithm ID
+        //   * length-prefixed bytes: digest of contents
+        // * length-prefixed sequence of certificates:
+        //   * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
+        // * length-prefixed sequence of length-prefixed additional attributes:
+        //   * uint32: ID
+        //   * (length - 4) bytes: value
+        signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
+            encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
+            encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
+            // additional attributes
+            new byte[0],
+        });
+        signer.publicKey = encodedPublicKey;
+        signer.signatures = new ArrayList<>(signerConfig.signatureAlgorithms.size());
+        for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
+            Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams =
+                    signatureAlgorithm.getJcaSignatureAlgorithmAndParams();
+            String jcaSignatureAlgorithm = sigAlgAndParams.getFirst();
+            AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond();
+            byte[] signatureBytes;
+            try {
+                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
+                signature.initSign(signerConfig.privateKey);
+                if (jcaSignatureAlgorithmParams != null) {
+                    signature.setParameter(jcaSignatureAlgorithmParams);
+                }
+                signature.update(signer.signedData);
+                signatureBytes = signature.sign();
+            } catch (InvalidKeyException e) {
+                throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
+            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+                    | SignatureException e) {
+                throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
+            }
+
+            try {
+                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
+                signature.initVerify(publicKey);
+                if (jcaSignatureAlgorithmParams != null) {
+                    signature.setParameter(jcaSignatureAlgorithmParams);
+                }
+                signature.update(signer.signedData);
+                if (!signature.verify(signatureBytes)) {
+                    throw new SignatureException("Signature did not verify");
+                }
+            } catch (InvalidKeyException e) {
+                throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
+                        + " signature using public key from certificate", e);
+            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+                    | SignatureException e) {
+                throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
+                        + " signature using public key from certificate", e);
+            }
+
+            signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
+        }
+
+        // FORMAT:
+        // * length-prefixed signed data
+        // * length-prefixed sequence of length-prefixed signatures:
+        //   * uint32: signature algorithm ID
+        //   * length-prefixed bytes: signature of signed data
+        // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
+        return encodeAsSequenceOfLengthPrefixedElements(
+                new byte[][] {
+                    signer.signedData,
+                    encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
+                            signer.signatures),
+                    signer.publicKey,
+                });
+    }
+
+    private static final class V2SignatureSchemeBlock {
+        private static final class Signer {
+            public byte[] signedData;
+            public List<Pair<Integer, byte[]>> signatures;
+            public byte[] publicKey;
+        }
+
+        private static final class SignedData {
+            public List<Pair<Integer, byte[]>> digests;
+            public List<byte[]> certificates;
+        }
+    }
+
+    private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {
+        byte[] encodedPublicKey = null;
+        if ("X.509".equals(publicKey.getFormat())) {
+            encodedPublicKey = publicKey.getEncoded();
+        }
+        if (encodedPublicKey == null) {
+            try {
+                encodedPublicKey =
+                        KeyFactory.getInstance(publicKey.getAlgorithm())
+                                .getKeySpec(publicKey, X509EncodedKeySpec.class)
+                                .getEncoded();
+            } catch (NoSuchAlgorithmException e) {
+                throw new InvalidKeyException(
+                        "Failed to obtain X.509 encoded form of public key " + publicKey
+                                + " of class " + publicKey.getClass().getName(),
+                        e);
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(
+                        "Failed to obtain X.509 encoded form of public key " + publicKey
+                                + " of class " + publicKey.getClass().getName(),
+                        e);
+            }
+        }
+        if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
+            throw new InvalidKeyException(
+                    "Failed to obtain X.509 encoded form of public key " + publicKey
+                            + " of class " + publicKey.getClass().getName());
+        }
+        return encodedPublicKey;
+    }
+
+    private static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
+            throws CertificateEncodingException {
+        List<byte[]> result = new ArrayList<>(certificates.size());
+        for (X509Certificate certificate : certificates) {
+            result.add(certificate.getEncoded());
+        }
+        return result;
+    }
+
+    private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
+        return encodeAsSequenceOfLengthPrefixedElements(
+                sequence.toArray(new byte[sequence.size()][]));
+    }
+
+    private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
+        int payloadSize = 0;
+        for (byte[] element : sequence) {
+            payloadSize += 4 + element.length;
+        }
+        ByteBuffer result = ByteBuffer.allocate(payloadSize);
+        result.order(ByteOrder.LITTLE_ENDIAN);
+        for (byte[] element : sequence) {
+            result.putInt(element.length);
+            result.put(element);
+        }
+        return result.array();
+      }
+
+    private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
+            List<Pair<Integer, byte[]>> sequence) {
+        int resultSize = 0;
+        for (Pair<Integer, byte[]> element : sequence) {
+            resultSize += 12 + element.getSecond().length;
+        }
+        ByteBuffer result = ByteBuffer.allocate(resultSize);
+        result.order(ByteOrder.LITTLE_ENDIAN);
+        for (Pair<Integer, byte[]> element : sequence) {
+            byte[] second = element.getSecond();
+            result.putInt(8 + second.length);
+            result.putInt(element.getFirst());
+            result.putInt(second.length);
+            result.put(second);
+        }
+        return result.array();
+    }
+
+    private static ByteBuffer copyToByteBuffer(DataSource dataSource) throws IOException {
+        long dataSourceSize = dataSource.size();
+        if (dataSourceSize > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException("Data source too large: " + dataSourceSize);
+        }
+        ByteBuffer result = ByteBuffer.allocate((int) dataSourceSize);
+        dataSource.feed(0, result.remaining(), new ByteBufferSink(result));
+        result.position(0);
+        return result;
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java
new file mode 100644
index 0000000..449953a
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.jar;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.Attributes;
+
+/**
+ * Producer of {@code META-INF/MANIFEST.MF} file.
+ */
+public abstract class ManifestWriter {
+
+    private static final byte[] CRLF = new byte[] {'\r', '\n'};
+    private static final int MAX_LINE_LENGTH = 70;
+
+    private ManifestWriter() {}
+
+    public static void writeMainSection(OutputStream out, Attributes attributes)
+            throws IOException {
+
+        // Main section must start with the Manifest-Version attribute.
+        // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
+        String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION);
+        if (manifestVersion == null) {
+            throw new IllegalArgumentException(
+                    "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing");
+        }
+        writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion);
+
+        if (attributes.size() > 1) {
+            SortedMap<String, String> namedAttributes = getAttributesSortedByName(attributes);
+            namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString());
+            writeAttributes(out, namedAttributes);
+        }
+        writeSectionDelimiter(out);
+    }
+
+    public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
+            throws IOException {
+        writeAttribute(out, "Name", name);
+
+        if (!attributes.isEmpty()) {
+            writeAttributes(out, getAttributesSortedByName(attributes));
+        }
+        writeSectionDelimiter(out);
+    }
+
+    static void writeSectionDelimiter(OutputStream out) throws IOException {
+        out.write(CRLF);
+    }
+
+    static void writeAttribute(OutputStream  out, Attributes.Name name, String value)
+            throws IOException {
+        writeAttribute(out, name.toString(), value);
+    }
+
+    private static void writeAttribute(OutputStream  out, String name, String value)
+            throws IOException {
+        writeLine(out, name + ": " + value);
+    }
+
+    private static void writeLine(OutputStream  out, String line) throws IOException {
+        byte[] lineBytes = line.getBytes("UTF-8");
+        int offset = 0;
+        int remaining = lineBytes.length;
+        boolean firstLine = true;
+        while (remaining > 0) {
+            int chunkLength;
+            if (firstLine) {
+                // First line
+                chunkLength = Math.min(remaining, MAX_LINE_LENGTH);
+            } else {
+                // Continuation line
+                out.write(CRLF);
+                out.write(' ');
+                chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1);
+            }
+            out.write(lineBytes, offset, chunkLength);
+            offset += chunkLength;
+            remaining -= chunkLength;
+            firstLine = false;
+        }
+        out.write(CRLF);
+    }
+
+    static SortedMap<String, String> getAttributesSortedByName(Attributes attributes) {
+        Set<Map.Entry<Object, Object>> attributesEntries = attributes.entrySet();
+        SortedMap<String, String> namedAttributes = new TreeMap<String, String>();
+        for (Map.Entry<Object, Object> attribute : attributesEntries) {
+            String attrName = attribute.getKey().toString();
+            String attrValue = attribute.getValue().toString();
+            namedAttributes.put(attrName, attrValue);
+        }
+        return namedAttributes;
+    }
+
+    static void writeAttributes(
+            OutputStream out, SortedMap<String, String> attributesSortedByName) throws IOException {
+        for (Map.Entry<String, String> attribute : attributesSortedByName.entrySet()) {
+            String attrName = attribute.getKey();
+            String attrValue = attribute.getValue();
+            writeAttribute(out, attrName, attrValue);
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java
new file mode 100644
index 0000000..9cd25f3
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.jar;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.SortedMap;
+import java.util.jar.Attributes;
+
+/**
+ * Producer of JAR signature file ({@code *.SF}).
+ */
+public abstract class SignatureFileWriter {
+    private SignatureFileWriter() {}
+
+    public static void writeMainSection(OutputStream out, Attributes attributes)
+            throws IOException {
+
+        // Main section must start with the Signature-Version attribute.
+        // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
+        String signatureVersion = attributes.getValue(Attributes.Name.SIGNATURE_VERSION);
+        if (signatureVersion == null) {
+            throw new IllegalArgumentException(
+                    "Mandatory " + Attributes.Name.SIGNATURE_VERSION + " attribute missing");
+        }
+        ManifestWriter.writeAttribute(out, Attributes.Name.SIGNATURE_VERSION, signatureVersion);
+
+        if (attributes.size() > 1) {
+            SortedMap<String, String> namedAttributes =
+                    ManifestWriter.getAttributesSortedByName(attributes);
+            namedAttributes.remove(Attributes.Name.SIGNATURE_VERSION.toString());
+            ManifestWriter.writeAttributes(out, namedAttributes);
+        }
+        writeSectionDelimiter(out);
+    }
+
+    public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
+            throws IOException {
+        ManifestWriter.writeIndividualSection(out, name, attributes);
+    }
+
+    public static void writeSectionDelimiter(OutputStream out) throws IOException {
+        ManifestWriter.writeSectionDelimiter(out);
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
new file mode 100644
index 0000000..76f4fda
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.util;
+
+import com.android.apksigner.core.util.DataSink;
+import com.android.apksigner.core.util.DataSource;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * {@link DataSource} backed by a {@link ByteBuffer}.
+ */
+public class ByteBufferDataSource implements DataSource {
+
+    private final ByteBuffer mBuffer;
+    private final long mSize;
+
+    /**
+     * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided
+     * buffer between the buffer's position and limit.
+     */
+    public ByteBufferDataSource(ByteBuffer buffer) {
+        mBuffer = buffer.slice();
+        mSize = buffer.remaining();
+    }
+
+    @Override
+    public long size() {
+        return mSize;
+    }
+
+    @Override
+    public void feed(long offset, int size, DataSink sink) throws IOException {
+        if (offset < 0) {
+            throw new IllegalArgumentException("offset: " + offset);
+        }
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        if (offset > mSize) {
+            throw new IllegalArgumentException(
+                    "offset (" + offset + ") > source size (" + mSize + ")");
+        }
+        long endOffset = offset + size;
+        if (endOffset < offset) {
+            throw new IllegalArgumentException(
+                    "offset (" + offset + ") + size (" + size + ") overflow");
+        }
+        if (endOffset > mSize) {
+            throw new IllegalArgumentException(
+                    "offset (" + offset + ") + size (" + size + ") > source size (" + mSize  +")");
+        }
+
+        int chunkPosition = (int) offset; // safe to downcast because mSize <= Integer.MAX_VALUE
+        int chunkLimit = (int) endOffset; // safe to downcast because mSize <= Integer.MAX_VALUE
+        ByteBuffer chunk;
+        // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position
+        // and limit fields, to be more specific). We thus use synchronization around these
+        // state-changing operations to make instances of this class thread-safe.
+        synchronized (mBuffer) {
+            // ByteBuffer.limit(int) and .position(int) check that that the position >= limit
+            // invariant is not broken. Thus, the only way to safely change position and limit
+            // without caring about their current values is to first set position to 0 or set the
+            // limit to capacity.
+            mBuffer.position(0);
+
+            mBuffer.limit(chunkLimit);
+            mBuffer.position(chunkPosition);
+            chunk = mBuffer.slice();
+        }
+
+        sink.consume(chunk);
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java
new file mode 100644
index 0000000..8c57905
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.util;
+
+import com.android.apksigner.core.util.DataSink;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Data sink which stores all received data into the associated {@link ByteBuffer}.
+ */
+public class ByteBufferSink implements DataSink {
+
+    private final ByteBuffer mBuffer;
+
+    public ByteBufferSink(ByteBuffer buffer) {
+        mBuffer = buffer;
+    }
+
+    @Override
+    public void consume(byte[] buf, int offset, int length) throws IOException {
+        try {
+            mBuffer.put(buf, offset, length);
+        } catch (BufferOverflowException e) {
+            throw new IOException(
+                    "Insufficient space in output buffer for " + length + " bytes", e);
+        }
+    }
+
+    @Override
+    public void consume(ByteBuffer buf) throws IOException {
+        int length = buf.remaining();
+        try {
+            mBuffer.put(buf);
+        } catch (BufferOverflowException e) {
+            throw new IOException(
+                    "Insufficient space in output buffer for " + length + " bytes", e);
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java
new file mode 100644
index 0000000..d59af41
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.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/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
new file mode 100644
index 0000000..7b47e50
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.internal.zip;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Assorted ZIP format helpers.
+ *
+ * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
+ * order of these buffers is little-endian.
+ */
+public abstract class ZipUtils {
+    private ZipUtils() {}
+
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+
+    /**
+     * Sets the offset of the start of the ZIP Central Directory in the archive.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static void setZipEocdCentralDirectoryOffset(
+            ByteBuffer zipEndOfCentralDirectory, long offset) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        setUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
+                offset);
+    }
+
+    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
+        if ((value < 0) || (value > 0xffffffffL)) {
+            throw new IllegalArgumentException("uint32 value of out range: " + value);
+        }
+        buffer.putInt(buffer.position() + offset, (int) value);
+    }
+}
\ No newline at end of file
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java
new file mode 100644
index 0000000..35a61fc
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Consumer of input data which may be provided in one go or in chunks.
+ */
+public interface DataSink {
+
+    /**
+     * Consumes the provided chunk of data.
+     *
+     * <p>This data sink guarantees to not hold references to the provided buffer after this method
+     * terminates.
+     */
+    void consume(byte[] buf, int offset, int length) throws IOException;
+
+    /**
+     * Consumes all remaining data in the provided buffer and advances the buffer's position
+     * to the buffer's limit.
+     *
+     * <p>This data sink guarantees to not hold references to the provided buffer after this method
+     * terminates.
+     */
+    void consume(ByteBuffer buf) throws IOException;
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
new file mode 100644
index 0000000..04560cb
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.apksigner.core.util;
+
+import java.io.IOException;
+
+/**
+ * Abstract representation of a source of data.
+ *
+ * <p>This abstraction serves three purposes:
+ * <ul>
+ * <li>Transparent handling of different types of sources, such as {@code byte[]},
+ *     {@link java.nio.ByteBuffer}, {@link java.io.RandomAccessFile}, memory-mapped file.</li>
+ * <li>Support sources larger than 2 GB. If all sources were smaller than 2 GB, {@code ByteBuffer}
+ *     may have worked as the unifying abstraction.</li>
+ * <li>Support sources which do not fit into logical memory as a contiguous region.</li>
+ * </ul>
+ */
+public interface DataSource {
+
+    /**
+     * Returns the amount of data (in bytes) contained in this data source.
+     */
+    long size();
+
+    /**
+     * Feeds the specified chunk from this data source into the provided sink.
+     *
+     * @param offset index (in bytes) at which the chunk starts inside data source
+     * @param size size (in bytes) of the chunk
+     */
+    void feed(long offset, int size, DataSink sink) throws IOException;
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
new file mode 100644
index 0000000..978afae
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
@@ -0,0 +1,23 @@
+package com.android.apksigner.core.util;
+
+import com.android.apksigner.core.internal.util.ByteBufferDataSource;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility methods for working with {@link DataSource} abstraction.
+ */
+public abstract class DataSources {
+    private DataSources() {}
+
+    /**
+     * Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source
+     * represents the data contained between the position and limit of the buffer.
+     */
+    public static DataSource asDataSource(ByteBuffer buffer) {
+        if (buffer == null) {
+            throw new NullPointerException();
+        }
+        return new ByteBufferDataSource(buffer);
+    }
+}
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 69f17e2..c80d93c 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -82,6 +82,7 @@
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 import java.util.regex.Pattern;
+
 import javax.crypto.Cipher;
 import javax.crypto.EncryptedPrivateKeyInfo;
 import javax.crypto.SecretKeyFactory;
@@ -126,34 +127,29 @@
     private static final String APK_SIG_SCHEME_V2_DIGEST_ALGORITHM = "SHA-256";
 
     /**
-     * Minimum Android SDK API Level which accepts JAR signatures which use SHA-256. Older platform
-     * versions accept only SHA-1 signatures.
-     */
-    private static final int MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES = 18;
-
-    /**
      * Returns the digest algorithm ID (one of {@code USE_SHA1} or {@code USE_SHA256}) to be used
-     * for v1 signing (using JAR Signature Scheme) an APK using the private key corresponding to the
-     * provided certificate.
+     * for v1 signing (JAR signing) an APK using the private key corresponding to the provided
+     * certificate.
      *
      * @param minSdkVersion minimum Android platform API Level supported by the APK (see
      *        minSdkVersion attribute in AndroidManifest.xml). The higher the minSdkVersion, the
      *        stronger hash may be used for signing the APK.
      */
     private static int getV1DigestAlgorithmForApk(X509Certificate cert, int minSdkVersion) {
-        String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
-        if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) {
-            // see "HISTORICAL NOTE" above.
-            if (minSdkVersion < MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES) {
-                return USE_SHA1;
-            } else {
-                return USE_SHA256;
+        String keyAlgorithm = cert.getPublicKey().getAlgorithm();
+        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+            // RSA can be used only with SHA-1 prior to API Level 18.
+            return (minSdkVersion < 18) ? USE_SHA1 : USE_SHA256;
+        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+            // ECDSA cannot be used prior to API Level 18 at all. It can only be used with SHA-1
+            // on API Levels 18, 19, and 20.
+            if (minSdkVersion < 18) {
+                throw new IllegalArgumentException(
+                        "ECDSA signatures only supported for minSdkVersion 18 and higher");
             }
-        } else if (sigAlg.startsWith("SHA256WITH")) {
-            return USE_SHA256;
+            return (minSdkVersion < 21) ? USE_SHA1 : USE_SHA256;
         } else {
-            throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
-                                               "\" in cert [" + cert.getSubjectDN());
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
         }
     }
 
diff --git a/tools/warn.py b/tools/warn.py
index 14b3f48..d4b2f57 100755
--- a/tools/warn.py
+++ b/tools/warn.py
@@ -303,6 +303,89 @@
     { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
         'description':'Java: Unchecked conversion',
         'patterns':[r".*: warning: \[unchecked\] unchecked conversion"] },
+
+    # Warnings from error prone.
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Long literal suffix',
+        'patterns':[r".*: warning: \[LongLiteralLowerCaseSuffix\] Prefer 'L' to 'l' for the suffix to long literal"] },
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Missing @Deprecated',
+        'patterns':[r".*: warning: \[DepAnn\] Deprecated item is not annotated with @Deprecated"] },
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Use of deprecated member',
+        'patterns':[r".*: warning: \[deprecation\] .+ in .+ has been deprecated"] },
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Missing hashCode method',
+        'patterns':[r".*: warning: \[EqualsHashCode\] Classes that override equals should also override hashCode."] },
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Hashtable contains is a legacy method',
+        'patterns':[r".*: warning: \[HashtableContains\] contains\(\) is a legacy method that is equivalent to containsValue\(\)"] },
+    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
+        'description':'Java: Type parameter used only for return type',
+        'patterns':[r".*: warning: \[TypeParameterUnusedInFormals\] Declaring a type parameter that is only used in the return type is a misuse of generics: operations on the type parameter are unchecked, it hides unsafe casts at invocations of the method, and it interacts badly with method overload resolution."] },
+
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: reference equality used on arrays',
+        'patterns':[r".*: warning: \[ArrayEquals\] Reference equality used to compare arrays"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: hashcode used on array',
+        'patterns':[r".*: warning: \[ArrayHashCode\] hashcode method on array does not hash array contents"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: toString used on an array',
+        'patterns':[r".*: warning: \[ArrayToStringConcatenation\] Implicit toString used on an array \(String \+ Array\)",
+                    r".*: warning: \[ArrayToString\] Calling toString on an array does not provide useful information"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Exception created but not thrown',
+        'patterns':[r".*: warning: \[DeadException\] Exception created but not thrown"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Return or throw from a finally',
+        'patterns':[r".*: warning: \[Finally\] If you return or throw from a finally, then values returned or thrown from the try-catch block will be ignored. Consider using try-with-resources instead."] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Erroneous use of @GuardedBy',
+        'patterns':[r".*: warning: \[GuardedByChecker\] This access should be guarded by '.+'; instead found: '.+'",
+                    r".*: warning: \[GuardedByChecker\] This access should be guarded by '.+', which is not currently held"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Mislabeled Android string',
+        'patterns':[r".*: warning: \[MislabeledAndroidString\] .+ is not \".+\" but \".+\"; prefer .+ for clarity"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Missing cases in enum switch',
+        'patterns':[r".*: warning: \[MissingCasesInEnumSwitch\] Non-exhaustive switch, expected cases for: .+"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Multiple top-level classes (inhibits bug analysis)',
+        'patterns':[r".*: warning: \[MultipleTopLevelClasses\] Expected at most one top-level class declaration, instead found: .+"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: equals method doesn\'t override Object.equals',
+        'patterns':[r".*: warning: \[NonOverridingEquals\] equals method doesn't override Object\.equals.*"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Update of a volatile variable is non-atomic',
+        'patterns':[r".*: warning: \[NonAtomicVolatileUpdate\] This update of a volatile variable is non-atomic"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Return value ignored',
+        'patterns':[r".*: warning: \[ReturnValueIgnored\] Return value of this method must be used",
+                    r".*: warning: \[RectIntersectReturnValueIgnored\] Return value of android.graphics.Rect.intersect\(\) must be checked"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Static variable accessed from an object instance',
+        'patterns':[r".*: warning: \[StaticAccessedFromInstance\] Static (method|variable) .+ should not be accessed from an object instance; instead use .+"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Static guarded by instance',
+        'patterns':[r".*: warning: \[StaticGuardedByInstance\] Write to static variable should not be guarded by instance lock '.+'"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: String reference equality',
+        'patterns':[r".*: warning: \[StringEquality\] String comparison using reference equality instead of value equality"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Synchronization on non-final field',
+        'patterns':[r".*: warning: \[SynchronizeOnNonFinalField\] Synchronizing on non-final fields is not safe: if the field is ever updated, different threads may end up locking on different objects."] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Catch masks fail or assert',
+        'patterns':[r".*: warning: \[TryFailThrowable\] Catching Throwable/Error masks failures from fail\(\) or assert\*\(\) in the try block"] },
+    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
+        'description':'Java: Wait not in a loop',
+        'patterns':[r".*: warning: \[WaitNotInLoop\] Because of spurious wakeups, a?wait.*\(.*\) must always be called in a loop"] },
+
+    { 'category':'java',    'severity':severity.UNKNOWN,   'members':[], 'option':'',
+        'description':'Java: Unclassified/unrecognized warnings',
+        'patterns':[r".*: warning: \[.+\] .+"] },
+
     { 'category':'aapt',    'severity':severity.MEDIUM,   'members':[], 'option':'',
         'description':'aapt: No default translation',
         'patterns':[r".*: warning: string '.+' has no default translation in .*"] },
@@ -874,6 +957,9 @@
 # dump the html output to stdout
 dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant)
 dumpstats()
+# sort table based on number of members once dumpstats has deduplicated the
+# members.
+warnpatterns.sort(reverse=True, key=lambda i: len(i['members']))
 dumptoc()
 dumpseverity(severity.FIXMENOW)
 dumpseverity(severity.HIGH)
@@ -883,4 +969,3 @@
 dumpseverity(severity.HARMLESS)
 dumpseverity(severity.UNKNOWN)
 dumpfixed()
-