Merge "Include extra licenses in vendor image NOTICE file"
diff --git a/Usage.txt b/Usage.txt
index 558329b..ea4788a 100644
--- a/Usage.txt
+++ b/Usage.txt
@@ -26,12 +26,6 @@
       If no targets are specified, the build system will build the images
       for the configured product and variant.
 
-  An alternative to setting $TARGET_PRODUCT and $TARGET_BUILD_VARIANT,
-  which you may see in build servers, is to execute:
-
-    m PRODUCT-<product>-<variant>
-
-
   A target may be a file path. For example, out/host/linux-x86/bin/adb .
     Note that when giving a relative file path as a target, that path is
     interpreted relative to the root of the source tree (rather than relative
diff --git a/core/binary.mk b/core/binary.mk
index 5d2f965..531e4c3 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -112,6 +112,8 @@
 my_ndk_sysroot :=
 my_ndk_sysroot_include :=
 my_ndk_sysroot_lib :=
+my_api_level := 10000
+
 ifneq ($(LOCAL_SDK_VERSION),)
   ifdef LOCAL_IS_HOST_MODULE
     $(error $(LOCAL_PATH): LOCAL_SDK_VERSION cannot be used in host module)
@@ -147,20 +149,14 @@
     my_ndk_api := $(call math_max,$(my_ndk_api),$(my_min_sdk_version))
   endif
 
-  my_ndk_api_def := $(my_ndk_api)
   my_ndk_hist_api := $(my_ndk_api)
   ifeq ($(my_ndk_api),current)
-    my_ndk_api_def := __ANDROID_API_FUTURE__
     # The last API level supported by the old prebuilt NDKs.
     my_ndk_hist_api := 24
+  else
+    my_api_level := $(my_ndk_api)
   endif
 
-
-  # Traditionally this has come from android/api-level.h, but with the libc
-  # headers unified it must be set by the build system since we don't have
-  # per-API level copies of that header now.
-  my_cflags += -D__ANDROID_API__=$(my_ndk_api_def)
-
   my_ndk_source_root := \
       $(HISTORICAL_NDK_VERSIONS_ROOT)/$(LOCAL_NDK_VERSION)/sources
   my_ndk_sysroot := \
@@ -283,14 +279,14 @@
 
 ifneq ($(LOCAL_USE_VNDK),)
   # Required VNDK version for vendor modules is BOARD_VNDK_VERSION.
-  my_vndk_version := $(BOARD_VNDK_VERSION)
-  ifeq ($(my_vndk_version),current)
+  my_api_level := $(BOARD_VNDK_VERSION)
+  ifeq ($(my_api_level),current)
     # Build with current PLATFORM_VNDK_VERSION.
     # If PLATFORM_VNDK_VERSION has a CODENAME, it will return
     # __ANDROID_API_FUTURE__.
-    my_vndk_version := $(call codename-or-sdk-to-sdk,$(PLATFORM_VNDK_VERSION))
+    my_api_level := $(call codename-or-sdk-to-sdk,$(PLATFORM_VNDK_VERSION))
   endif
-  my_cflags += -D__ANDROID_API__=$(my_vndk_version) -D__ANDROID_VNDK__
+  my_cflags += -D__ANDROID_VNDK__
 endif
 
 ifndef LOCAL_IS_HOST_MODULE
@@ -1625,6 +1621,16 @@
   my_target_global_ldflags := $($(LOCAL_2ND_ARCH_VAR_PREFIX)CLANG_$(my_prefix)GLOBAL_LDFLAGS)
 endif # my_use_clang_lld
 
+my_target_triple := $($(LOCAL_2ND_ARCH_VAR_PREFIX)CLANG_$(my_prefix)TRIPLE)
+ifndef LOCAL_IS_HOST_MODULE
+  my_target_triple_flag := -target $(my_target_triple)$(my_api_level)
+else
+  my_target_triple_flag := -target $(my_target_triple)
+endif
+my_asflags += $(my_target_triple_flag)
+my_cflags += $(my_target_triple_flag)
+my_ldflags += $(my_target_triple_flag)
+
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_GLOBAL_C_INCLUDES := $(my_target_global_c_includes)
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_GLOBAL_C_SYSTEM_INCLUDES := $(my_target_global_c_system_includes)
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_TARGET_GLOBAL_CFLAGS := $(my_target_global_cflags)
diff --git a/core/board_config.mk b/core/board_config.mk
index f7dc557..4045c71 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -193,20 +193,29 @@
 # Note that this assumes that the 2ND_CPU_ABI for a 64 bit target
 # is always 32 bits. If this isn't the case, these variables should
 # be overriden in the board configuration.
+#
+# Similarly, TARGET_NATIVE_BRIDGE_2ND_ABI for a 64 bit target is always
+# 32 bits. Note that all CPU_ABIs are preferred over all NATIVE_BRIDGE_ABIs.
+_target_native_bridge_abi_list_32_bit :=
+_target_native_bridge_abi_list_64_bit :=
+
 ifeq (,$(TARGET_CPU_ABI_LIST_64_BIT))
   ifeq (true|true,$(TARGET_IS_64_BIT)|$(TARGET_SUPPORTS_64_BIT_APPS))
     TARGET_CPU_ABI_LIST_64_BIT := $(TARGET_CPU_ABI) $(TARGET_CPU_ABI2)
+    _target_native_bridge_abi_list_64_bit := $(TARGET_NATIVE_BRIDGE_ABI)
   endif
 endif
 
 ifeq (,$(TARGET_CPU_ABI_LIST_32_BIT))
   ifneq (true,$(TARGET_IS_64_BIT))
     TARGET_CPU_ABI_LIST_32_BIT := $(TARGET_CPU_ABI) $(TARGET_CPU_ABI2)
+    _target_native_bridge_abi_list_32_bit := $(TARGET_NATIVE_BRIDGE_ABI)
   else
     ifeq (true,$(TARGET_SUPPORTS_32_BIT_APPS))
       # For a 64 bit target, assume that the 2ND_CPU_ABI
       # is a 32 bit ABI.
       TARGET_CPU_ABI_LIST_32_BIT := $(TARGET_2ND_CPU_ABI) $(TARGET_2ND_CPU_ABI2)
+      _target_native_bridge_abi_list_32_bit := $(TARGET_NATIVE_BRIDGE_2ND_ABI)
     endif
   endif
 endif
@@ -215,14 +224,21 @@
 # of preference) that the target supports. If a TARGET_CPU_ABI_LIST
 # is specified by the board configuration, we use that. If not, we
 # build a list out of the TARGET_CPU_ABIs specified by the config.
+# Add NATIVE_BRIDGE_ABIs at the end to keep order of preference.
 ifeq (,$(TARGET_CPU_ABI_LIST))
   ifeq ($(TARGET_IS_64_BIT)|$(TARGET_PREFER_32_BIT_APPS),true|true)
-    TARGET_CPU_ABI_LIST := $(TARGET_CPU_ABI_LIST_32_BIT) $(TARGET_CPU_ABI_LIST_64_BIT)
+    TARGET_CPU_ABI_LIST := $(TARGET_CPU_ABI_LIST_32_BIT) $(TARGET_CPU_ABI_LIST_64_BIT) \
+                           $(_target_native_bridge_abi_list_32_bit) $(_target_native_bridge_abi_list_64_bit)
   else
-    TARGET_CPU_ABI_LIST := $(TARGET_CPU_ABI_LIST_64_BIT) $(TARGET_CPU_ABI_LIST_32_BIT)
+    TARGET_CPU_ABI_LIST := $(TARGET_CPU_ABI_LIST_64_BIT) $(TARGET_CPU_ABI_LIST_32_BIT) \
+                           $(_target_native_bridge_abi_list_64_bit) $(_target_native_bridge_abi_list_32_bit)
   endif
 endif
 
+# Add NATIVE_BRIDGE_ABIs at the end of 32 and 64 bit CPU_ABIs to keep order of preference.
+TARGET_CPU_ABI_LIST_32_BIT += $(_target_native_bridge_abi_list_32_bit)
+TARGET_CPU_ABI_LIST_64_BIT += $(_target_native_bridge_abi_list_64_bit)
+
 # Strip whitespace from the ABI list string.
 TARGET_CPU_ABI_LIST := $(subst $(space),$(comma),$(strip $(TARGET_CPU_ABI_LIST)))
 TARGET_CPU_ABI_LIST_32_BIT := $(subst $(space),$(comma),$(strip $(TARGET_CPU_ABI_LIST_32_BIT)))
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 05957cd..3e8fd3f 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -82,8 +82,6 @@
 
 # ---------------------------------------------------------------
 # The product defaults to generic on hardware
-# NOTE: This will be overridden in product_config.mk if make
-# was invoked with a PRODUCT-xxx-yyy goal.
 ifeq ($(TARGET_PRODUCT),)
 TARGET_PRODUCT := aosp_arm
 endif
@@ -94,6 +92,13 @@
 TARGET_BUILD_VARIANT := eng
 endif
 
+TARGET_BUILD_APPS ?=
+
+.KATI_READONLY := \
+  TARGET_PRODUCT \
+  TARGET_BUILD_VARIANT \
+  TARGET_BUILD_APPS
+
 # ---------------------------------------------------------------
 # Set up configuration for host machine.  We don't do cross-
 # compiles except for arm/mips, so the HOST is whatever we are
diff --git a/core/main.mk b/core/main.mk
index ff670dc..ecdbcbd 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -194,6 +194,8 @@
 $(error stopping)
 endif
 
+# These are the valid values of TARGET_BUILD_VARIANT.
+INTERNAL_VALID_VARIANTS := user userdebug eng
 ifneq ($(filter-out $(INTERNAL_VALID_VARIANTS),$(TARGET_BUILD_VARIANT)),)
 $(info ***************************************************************)
 $(info ***************************************************************)
@@ -1244,6 +1246,7 @@
   libnativebridge.so \
   libnativehelper.so \
   libnativeloader.so \
+  libneuralnetworks.so \
   libnpt.so \
   libopenjdk.so \
   libopenjdkjvm.so \
diff --git a/core/misc_prebuilt_internal.mk b/core/misc_prebuilt_internal.mk
index cdd5cd5..cc2683c 100644
--- a/core/misc_prebuilt_internal.mk
+++ b/core/misc_prebuilt_internal.mk
@@ -27,3 +27,5 @@
 
 $(LOCAL_BUILT_MODULE) : $(my_prebuilt_src_file)
 	$(transform-prebuilt-to-target)
+
+built_module := $(LOCAL_BUILT_MODULE)
\ No newline at end of file
diff --git a/core/ninja_config.mk b/core/ninja_config.mk
index 694c696..e33ebf5 100644
--- a/core/ninja_config.mk
+++ b/core/ninja_config.mk
@@ -7,7 +7,7 @@
 KATI_OUTPUT_PATTERNS := $(OUT_DIR)/build%.ninja $(OUT_DIR)/ninja%.sh
 
 # Modifier goals we don't need to pass to Ninja.
-NINJA_EXCLUDE_GOALS := all APP-% PRODUCT-%
+NINJA_EXCLUDE_GOALS := all
 
 # A list of goals which affect parsing of makefiles and we need to pass to Kati.
 PARSE_TIME_MAKE_GOALS := \
diff --git a/core/product_config.mk b/core/product_config.mk
index 360c79d..1293c94 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -78,86 +78,18 @@
 endef
 
 # ---------------------------------------------------------------
-
-# These are the valid values of TARGET_BUILD_VARIANT.  Also, if anything else is passed
-# as the variant in the PRODUCT-$TARGET_BUILD_PRODUCT-$TARGET_BUILD_VARIANT form,
-# it will be treated as a goal, and the eng variant will be used.
-INTERNAL_VALID_VARIANTS := user userdebug eng
-
-# ---------------------------------------------------------------
-# Provide "PRODUCT-<prodname>-<goal>" targets, which lets you build
-# a particular configuration without needing to set up the environment.
-#
+# Check for obsolete PRODUCT- and APP- goals
 ifeq ($(CALLED_FROM_SETUP),true)
 product_goals := $(strip $(filter PRODUCT-%,$(MAKECMDGOALS)))
 ifdef product_goals
-  # Scrape the product and build names out of the goal,
-  # which should be of the form PRODUCT-<productname>-<buildname>.
-  #
-  ifneq ($(words $(product_goals)),1)
-    $(error Only one PRODUCT-* goal may be specified; saw "$(product_goals)")
-  endif
-  goal_name := $(product_goals)
-  product_goals := $(patsubst PRODUCT-%,%,$(product_goals))
-  product_goals := $(subst -, ,$(product_goals))
-  ifneq ($(words $(product_goals)),2)
-    $(error Bad PRODUCT-* goal "$(goal_name)")
-  endif
-
-  # The product they want
-  TARGET_PRODUCT := $(word 1,$(product_goals))
-
-  # The variant they want
-  TARGET_BUILD_VARIANT := $(word 2,$(product_goals))
-
-  ifeq ($(TARGET_BUILD_VARIANT),tests)
-    $(error "tests" has been deprecated as a build variant. Use it as a build goal instead.)
-  endif
-
-  # The build server wants to do make PRODUCT-dream-sdk
-  # which really means TARGET_PRODUCT=dream make sdk.
-  ifneq ($(filter-out $(INTERNAL_VALID_VARIANTS),$(TARGET_BUILD_VARIANT)),)
-    override MAKECMDGOALS := $(MAKECMDGOALS) $(TARGET_BUILD_VARIANT)
-    TARGET_BUILD_VARIANT := userdebug
-    default_goal_substitution :=
-  else
-    default_goal_substitution := droid
-  endif
-
-  # Replace the PRODUCT-* goal with the build goal that it refers to.
-  # Note that this will ensure that it appears in the same relative
-  # position, in case it matters.
-  override MAKECMDGOALS := $(patsubst $(goal_name),$(default_goal_substitution),$(MAKECMDGOALS))
+  $(error The PRODUCT-* goal is no longer supported. Use `TARGET_PRODUCT=<product> m droid` instead)
 endif
-endif # CALLED_FROM_SETUP
-# else: Use the value set in the environment or buildspec.mk.
-
-# ---------------------------------------------------------------
-# Provide "APP-<appname>" targets, which lets you build
-# an unbundled app.
-#
-ifeq ($(CALLED_FROM_SETUP),true)
 unbundled_goals := $(strip $(filter APP-%,$(MAKECMDGOALS)))
 ifdef unbundled_goals
-  ifneq ($(words $(unbundled_goals)),1)
-    $(error Only one APP-* goal may be specified; saw "$(unbundled_goals)")
-  endif
-  TARGET_BUILD_APPS := $(strip $(subst -, ,$(patsubst APP-%,%,$(unbundled_goals))))
-  ifneq ($(filter droid,$(MAKECMDGOALS)),)
-    override MAKECMDGOALS := $(patsubst $(unbundled_goals),,$(MAKECMDGOALS))
-  else
-    override MAKECMDGOALS := $(patsubst $(unbundled_goals),droid,$(MAKECMDGOALS))
-  endif
+  $(error The APP-* goal is no longer supported. Use `TARGET_BUILD_APPS="<app>" m droid` instead)
 endif # unbundled_goals
 endif
 
-# Now that we've parsed APP-* and PRODUCT-*, mark these as readonly
-TARGET_BUILD_APPS ?=
-.KATI_READONLY := \
-  TARGET_PRODUCT \
-  TARGET_BUILD_VARIANT \
-  TARGET_BUILD_APPS
-
 # Default to building dalvikvm on hosts that support it...
 ifeq ($(HOST_OS),linux)
 # ... or if the if the option is already set
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 3280f0a..c256641 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -19,6 +19,9 @@
     abb \
     adbd \
     am \
+    android.hardware.neuralnetworks@1.0 \
+    android.hardware.neuralnetworks@1.1 \
+    android.hardware.neuralnetworks@1.2 \
     android.hidl.allocator@1.0-service \
     android.hidl.base-V1.0-java \
     android.hidl.manager-V1.0-java \
@@ -53,6 +56,7 @@
     com.android.conscrypt \
     com.android.location.provider \
     com.android.resolv \
+    com.android.neuralnetworks \
     com.android.tzdata \
     ContactsProvider \
     content \
@@ -154,7 +158,6 @@
     libnetd_client \
     libnetlink \
     libnetutils \
-    libneuralnetworks \
     libOpenMAXAL \
     libOpenSLES \
     libpdfium \
@@ -224,6 +227,7 @@
     resize2fs \
     rss_hwm_reset \
     run-as \
+    sanitizer.libraries.txt \
     schedtest \
     screencap \
     sdcard \
@@ -261,6 +265,8 @@
     viewcompiler \
     voip-common \
     vold \
+    vndkcore.libraries.txt \
+    vndkprivate.libraries.txt \
     WallpaperBackup \
     watchdogd \
     wificond \
diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk
index 5693234..1ef124b 100644
--- a/target/product/gsi/Android.mk
+++ b/target/product/gsi/Android.mk
@@ -38,6 +38,7 @@
 droidcore: check-vndk-list
 
 check-vndk-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-list-timestamp
+check-vndk-abi-dump-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-abi-dump-list-timestamp
 
 ifeq ($(TARGET_IS_64_BIT)|$(TARGET_2ND_ARCH),true|)
 # TODO(b/110429754) remove this condition when we support 64-bit-only device
@@ -50,6 +51,9 @@
 check-vndk-list: ;
 else
 check-vndk-list: $(check-vndk-list-timestamp)
+ifneq ($(SKIP_ABI_CHECKS),true)
+check-vndk-list: $(check-vndk-abi-dump-list-timestamp)
+endif
 endif
 
 _vndk_check_failure_message := " error: VNDK library list has been changed.\n"
@@ -97,12 +101,51 @@
 endif
 	@chmod a+x $@
 
+#####################################################################
+# Check that all ABI reference dumps have corresponding NDK/VNDK
+# libraries.
+
+# $(1): The directory containing ABI dumps.
+# Return a list of ABI dump paths ending with .so.lsdump.
+define find-abi-dump-paths
+$(if $(wildcard $(1)), \
+  $(addprefix $(1)/, \
+    $(call find-files-in-subdirs,$(1),"*.so.lsdump" -and -type f,.)))
+endef
+
+VNDK_ABI_DUMP_DIR := prebuilts/abi-dumps/vndk/$(PLATFORM_VNDK_VERSION)
+NDK_ABI_DUMP_DIR := prebuilts/abi-dumps/ndk/$(PLATFORM_VNDK_VERSION)
+VNDK_ABI_DUMPS := $(call find-abi-dump-paths,$(VNDK_ABI_DUMP_DIR))
+NDK_ABI_DUMPS := $(call find-abi-dump-paths,$(NDK_ABI_DUMP_DIR))
+
+$(check-vndk-abi-dump-list-timestamp): $(VNDK_ABI_DUMPS) $(NDK_ABI_DUMPS)
+	$(eval added_vndk_abi_dumps := $(strip $(sort $(filter-out \
+	  $(addsuffix .so.lsdump,$(VNDK_SAMEPROCESS_LIBRARIES) $(VNDK_CORE_LIBRARIES)), \
+	  $(notdir $(VNDK_ABI_DUMPS))))))
+	$(if $(added_vndk_abi_dumps), \
+	  echo -e "Found ABI reference dumps for non-VNDK libraries. Run \`find \$${ANDROID_BUILD_TOP}/$(VNDK_ABI_DUMP_DIR) '(' -name $(subst $(space), -or -name ,$(added_vndk_abi_dumps)) ')' -delete\` to delete the dumps.")
+
+	$(eval added_ndk_abi_dumps := $(strip $(sort $(filter-out \
+	  $(addsuffix .so.lsdump,$(NDK_MIGRATED_LIBS) $(LLNDK_LIBRARIES)), \
+	  $(notdir $(NDK_ABI_DUMPS))))))
+	$(if $(added_ndk_abi_dumps), \
+	  echo -e "Found ABI reference dumps for non-NDK libraries. Run \`find \$${ANDROID_BUILD_TOP}/$(NDK_ABI_DUMP_DIR) '(' -name $(subst $(space), -or -name ,$(added_ndk_abi_dumps)) ')' -delete\` to delete the dumps.")
+
+	$(if $(added_vndk_abi_dumps)$(added_ndk_abi_dumps),exit 1)
+	$(hide) mkdir -p $(dir $@)
+	$(hide) touch $@
+
+#####################################################################
+# VNDK package and snapshot.
+
 ifneq ($(BOARD_VNDK_VERSION),)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := vndk_package
+# Filter LLNDK libs moved to APEX to avoid pulling them into /system/LIB
 LOCAL_REQUIRED_MODULES := \
-    $(LLNDK_LIBRARIES)
+    $(filter-out $(LLNDK_MOVED_TO_APEX_LIBRARIES),$(LLNDK_LIBRARIES)))
+
 ifneq ($(TARGET_SKIP_CURRENT_VNDK),true)
 LOCAL_REQUIRED_MODULES += \
     llndk.libraries.txt \
diff --git a/tools/check_builds.sh b/tools/check_builds.sh
deleted file mode 100644
index 7e4ea7c..0000000
--- a/tools/check_builds.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright (C) 2009 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.
-
-#
-# Usage:
-#
-# Source this file into your environment.  Then:
-#
-#    $ golden_builds sdk-sdk generic-eng generic-userdebug dream-eng
-# 
-# will build a set of combos.  This might take a while.  Then you can
-# go make changes, and run:
-#
-#    $ check_builds sdk-sdk generic-eng generic-userdebug dream-eng
-#
-# Go get dinner, and when you get back, there will be a file
-# test-builds/sizes.html that has a pretty chart of which files are
-# in which tree, and how big they are.  In that chart, cells for files
-# that are missing are red, and rows where the file sizes are not all
-# the same will be blue.
-#
-
-TEST_BUILD_DIR=test-builds
-
-function do_builds
-{
-    PREFIX=$1
-    shift
-    while [ -n "$1" ]
-    do
-        rm -rf $TEST_BUILD_DIR/$PREFIX-$1
-        make PRODUCT-$(echo $1 | sed "s/-.*//" )-installclean
-        make -j16 PRODUCT-$1 dist DIST_DIR=$TEST_BUILD_DIR/$PREFIX-$1
-        if [ $? -ne 0 ] ; then
-            echo FAILED
-            return
-        fi
-        shift
-    done
-}
-
-function golden_builds
-{
-    rm -rf $TEST_BUILD_DIR/golden-* $TEST_BUILD_DIR/dist-*
-    do_builds golden "$@"
-}
-
-function compare_builds
-{
-    local inputs=
-    while [ -n "$1" ]
-    do
-        inputs="$inputs $TEST_BUILD_DIR/golden-$1/installed-files.txt"
-        inputs="$inputs $TEST_BUILD_DIR/dist-$1/installed-files.txt"
-        shift
-    done
-    build/make/tools/compare_fileslist.py $inputs > $TEST_BUILD_DIR/sizes.html
-}
-
-function check_builds
-{
-    rm -rf $TEST_BUILD_DIR/dist-*
-    do_builds dist "$@"
-    compare_builds "$@"
-}
-
-function diff_builds
-{
-    local inputs=
-    while [ -n "$1" ]
-    do
-        diff $TEST_BUILD_DIR/golden-$1/installed-files.txt $TEST_BUILD_DIR/dist-$1/installed-files.txt &> /dev/null
-        if [ $? != 0 ]; then
-            echo =========== $1 ===========
-            diff $TEST_BUILD_DIR/golden-$1/installed-files.txt $TEST_BUILD_DIR/dist-$1/installed-files.txt
-        fi
-        shift
-    done
-    build/make/tools/compare_fileslist.py $inputs > $TEST_BUILD_DIR/sizes.html
-}
-
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index d4c4673..40cdce8 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -106,6 +106,19 @@
     ],
 }
 
+python_binary_host {
+    name: "merge_builds",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [
+        "build_super_image.py",
+        "merge_builds.py",
+    ],
+    main: "merge_builds.py",
+    libs: [
+        "releasetools_common",
+    ],
+}
+
 python_defaults {
     name: "releasetools_test_defaults",
     srcs: [
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index e177828..bdb34b8 100755
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -810,11 +810,11 @@
       banner("recovery (two-step image)")
       # The special recovery.img for two-step package use.
       recovery_two_step_image = common.GetBootableImage(
-          "IMAGES/recovery-two-step.img", "recovery-two-step.img",
+          "OTA/recovery-two-step.img", "recovery-two-step.img",
           OPTIONS.input_tmp, "RECOVERY", two_step_image=True)
       assert recovery_two_step_image, "Failed to create recovery-two-step.img."
       recovery_two_step_image_path = os.path.join(
-          OPTIONS.input_tmp, "IMAGES", "recovery-two-step.img")
+          OPTIONS.input_tmp, "OTA", "recovery-two-step.img")
       if not os.path.exists(recovery_two_step_image_path):
         recovery_two_step_image.WriteToDir(OPTIONS.input_tmp)
         if output_zip:
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 913601f..1175688 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -554,6 +554,64 @@
     logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
 
 
+def MergeDynamicPartitionInfoDicts(framework_dict,
+                                   vendor_dict,
+                                   include_dynamic_partition_list=True,
+                                   size_prefix="",
+                                   size_suffix="",
+                                   list_prefix="",
+                                   list_suffix=""):
+  """Merges dynamic partition info variables.
+
+  Args:
+    framework_dict: The dictionary of dynamic partition info variables from the
+      partial framework target files.
+    vendor_dict: The dictionary of dynamic partition info variables from the
+      partial vendor target files.
+    include_dynamic_partition_list: If true, merges the dynamic_partition_list
+      variable. Not all use cases need this variable merged.
+    size_prefix: The prefix in partition group size variables that precedes the
+      name of the partition group. For example, partition group 'group_a' with
+      corresponding size variable 'super_group_a_group_size' would have the
+      size_prefix 'super_'.
+    size_suffix: Similar to size_prefix but for the variable's suffix. For
+      example, 'super_group_a_group_size' would have size_suffix '_group_size'.
+    list_prefix: Similar to size_prefix but for the partition group's
+      partition_list variable.
+    list_suffix: Similar to size_suffix but for the partition group's
+      partition_list variable.
+
+  Returns:
+    The merged dynamic partition info dictionary.
+  """
+  merged_dict = {}
+  # Partition groups and group sizes are defined by the vendor dict because
+  # these values may vary for each board that uses a shared system image.
+  merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
+  if include_dynamic_partition_list:
+    framework_dynamic_partition_list = framework_dict.get(
+        "dynamic_partition_list", "")
+    vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list",
+                                                    "")
+    merged_dict["dynamic_partition_list"] = (
+        "%s %s" % (framework_dynamic_partition_list,
+                   vendor_dynamic_partition_list)).strip()
+  for partition_group in merged_dict["super_partition_groups"].split(" "):
+    # Set the partition group's size using the value from the vendor dict.
+    key = "%s%s%s" % (size_prefix, partition_group, size_suffix)
+    if key not in vendor_dict:
+      raise ValueError("Vendor dict does not contain required key %s." % key)
+    merged_dict[key] = vendor_dict[key]
+
+    # Set the partition group's partition list using a concatenation of the
+    # framework and vendor partition lists.
+    key = "%s%s%s" % (list_prefix, partition_group, list_suffix)
+    merged_dict[key] = (
+        "%s %s" %
+        (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
+  return merged_dict
+
+
 def AppendAVBSigningArgs(cmd, partition):
   """Append signing arguments for avbtool."""
   # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index 8fb9871..e4c9852 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -103,8 +103,6 @@
       continue
     if not image.endswith(".img"):
       continue
-    if image == "recovery-two-step.img":
-      continue
     if OPTIONS.put_super:
       if image == "super_empty.img":
         continue
diff --git a/tools/releasetools/merge_builds.py b/tools/releasetools/merge_builds.py
new file mode 100644
index 0000000..7724d6f
--- /dev/null
+++ b/tools/releasetools/merge_builds.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2019 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.
+#
+"""Merges two non-dist partial builds together.
+
+Given two partial builds, a framework build and a vendor build, merge the builds
+together so that the images can be flashed using 'fastboot flashall'.
+
+To support both DAP and non-DAP vendor builds with a single framework partial
+build, the framework partial build should always be built with DAP enabled. The
+vendor partial build determines whether the merged result supports DAP.
+
+This script does not require builds to be built with 'make dist'.
+This script assumes that images other than super_empty.img do not require
+regeneration, including vbmeta images.
+TODO(b/137853921): Add support for regenerating vbmeta images.
+
+Usage: merge_builds.py [args]
+
+  --framework_images comma_separated_image_list
+      Comma-separated list of image names that should come from the framework
+      build.
+
+  --product_out_framework product_out_framework_path
+      Path to out/target/product/<framework build>.
+
+  --product_out_vendor product_out_vendor_path
+      Path to out/target/product/<vendor build>.
+"""
+from __future__ import print_function
+
+import logging
+import os
+import sys
+
+import build_super_image
+import common
+
+logger = logging.getLogger(__name__)
+
+OPTIONS = common.OPTIONS
+OPTIONS.framework_images = ("system",)
+OPTIONS.product_out_framework = None
+OPTIONS.product_out_vendor = None
+
+
+def CreateImageSymlinks():
+  for image in OPTIONS.framework_images:
+    image_path = os.path.join(OPTIONS.product_out_framework, "%s.img" % image)
+    symlink_path = os.path.join(OPTIONS.product_out_vendor, "%s.img" % image)
+    if os.path.exists(symlink_path):
+      if os.path.islink(symlink_path):
+        os.remove(symlink_path)
+      else:
+        raise ValueError("Attempting to overwrite built image: %s" %
+                         symlink_path)
+    os.symlink(image_path, symlink_path)
+
+
+def BuildSuperEmpty():
+  framework_dict = common.LoadDictionaryFromFile(
+      os.path.join(OPTIONS.product_out_framework, "misc_info.txt"))
+  vendor_dict = common.LoadDictionaryFromFile(
+      os.path.join(OPTIONS.product_out_vendor, "misc_info.txt"))
+  # Regenerate super_empty.img if both partial builds enable DAP. If only the
+  # the vendor build enables DAP, the vendor build's existing super_empty.img
+  # will be reused. If only the framework build should enable DAP, super_empty
+  # should be included in the --framework_images flag to copy the existing
+  # super_empty.img from the framework build.
+  if (framework_dict.get("use_dynamic_partitions") == "true") and (
+      vendor_dict.get("use_dynamic_partitions") == "true"):
+    merged_dict = dict(vendor_dict)
+    merged_dict.update(
+        common.MergeDynamicPartitionInfoDicts(
+            framework_dict=framework_dict,
+            vendor_dict=vendor_dict,
+            size_prefix="super_",
+            size_suffix="_group_size",
+            list_prefix="super_",
+            list_suffix="_partition_list"))
+    output_super_empty_path = os.path.join(OPTIONS.product_out_vendor,
+                                           "super_empty.img")
+    build_super_image.BuildSuperImage(merged_dict, output_super_empty_path)
+
+
+def MergeBuilds():
+  CreateImageSymlinks()
+  BuildSuperEmpty()
+  # TODO(b/137853921): Add support for regenerating vbmeta images.
+
+
+def main():
+  common.InitLogging()
+
+  def option_handler(o, a):
+    if o == "--framework_images":
+      OPTIONS.framework_images = [i.strip() for i in a.split(",")]
+    elif o == "--product_out_framework":
+      OPTIONS.product_out_framework = a
+    elif o == "--product_out_vendor":
+      OPTIONS.product_out_vendor = a
+    else:
+      return False
+    return True
+
+  args = common.ParseOptions(
+      sys.argv[1:],
+      __doc__,
+      extra_long_opts=[
+          "framework_images=",
+          "product_out_framework=",
+          "product_out_vendor=",
+      ],
+      extra_option_handler=option_handler)
+
+  if (args or OPTIONS.product_out_framework is None or
+      OPTIONS.product_out_vendor is None):
+    common.Usage(__doc__)
+    sys.exit(1)
+
+  MergeBuilds()
+
+
+if __name__ == "__main__":
+  main()
diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py
index 7343f38..81b95c8 100755
--- a/tools/releasetools/merge_target_files.py
+++ b/tools/releasetools/merge_target_files.py
@@ -402,64 +402,6 @@
             'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
 
 
-def merge_dynamic_partition_info_dicts(framework_dict,
-                                       vendor_dict,
-                                       include_dynamic_partition_list=True,
-                                       size_prefix='',
-                                       size_suffix='',
-                                       list_prefix='',
-                                       list_suffix=''):
-  """Merges dynamic partition info variables.
-
-  Args:
-    framework_dict: The dictionary of dynamic partition info variables from the
-      partial framework target files.
-    vendor_dict: The dictionary of dynamic partition info variables from the
-      partial vendor target files.
-    include_dynamic_partition_list: If true, merges the dynamic_partition_list
-      variable. Not all use cases need this variable merged.
-    size_prefix: The prefix in partition group size variables that precedes the
-      name of the partition group. For example, partition group 'group_a' with
-      corresponding size variable 'super_group_a_group_size' would have the
-      size_prefix 'super_'.
-    size_suffix: Similar to size_prefix but for the variable's suffix. For
-      example, 'super_group_a_group_size' would have size_suffix '_group_size'.
-    list_prefix: Similar to size_prefix but for the partition group's
-      partition_list variable.
-    list_suffix: Similar to size_suffix but for the partition group's
-      partition_list variable.
-
-  Returns:
-    The merged dynamic partition info dictionary.
-  """
-  merged_dict = {}
-  # Partition groups and group sizes are defined by the vendor dict because
-  # these values may vary for each board that uses a shared system image.
-  merged_dict['super_partition_groups'] = vendor_dict['super_partition_groups']
-  if include_dynamic_partition_list:
-    framework_dynamic_partition_list = framework_dict.get(
-        'dynamic_partition_list', '')
-    vendor_dynamic_partition_list = vendor_dict.get('dynamic_partition_list',
-                                                    '')
-    merged_dict['dynamic_partition_list'] = (
-        '%s %s' % (framework_dynamic_partition_list,
-                   vendor_dynamic_partition_list)).strip()
-  for partition_group in merged_dict['super_partition_groups'].split(' '):
-    # Set the partition group's size using the value from the vendor dict.
-    key = '%s%s%s' % (size_prefix, partition_group, size_suffix)
-    if key not in vendor_dict:
-      raise ValueError('Vendor dict does not contain required key %s.' % key)
-    merged_dict[key] = vendor_dict[key]
-
-    # Set the partition group's partition list using a concatenation of the
-    # framework and vendor partition lists.
-    key = '%s%s%s' % (list_prefix, partition_group, list_suffix)
-    merged_dict[key] = (
-        '%s %s' %
-        (framework_dict.get(key, ''), vendor_dict.get(key, ''))).strip()
-  return merged_dict
-
-
 def process_misc_info_txt(framework_target_files_temp_dir,
                           vendor_target_files_temp_dir,
                           output_target_files_temp_dir,
@@ -503,7 +445,7 @@
   # Merge misc info keys used for Dynamic Partitions.
   if (merged_dict.get('use_dynamic_partitions') == 'true') and (
       framework_dict.get('use_dynamic_partitions') == 'true'):
-    merged_dynamic_partitions_dict = merge_dynamic_partition_info_dicts(
+    merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
         framework_dict=framework_dict,
         vendor_dict=merged_dict,
         size_prefix='super_',
@@ -566,7 +508,7 @@
   vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
       os.path.join(vendor_target_files_dir, *dynamic_partitions_info_path))
 
-  merged_dynamic_partitions_dict = merge_dynamic_partition_info_dicts(
+  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
       framework_dict=framework_dynamic_partitions_dict,
       vendor_dict=vendor_dynamic_partitions_dict,
       # META/dynamic_partitions_info.txt does not use dynamic_partition_list.
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index db7e86c..4472b4a 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -240,7 +240,7 @@
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
 AB_PARTITIONS = 'META/ab_partitions.txt'
-UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'RADIO/*']
+UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
 RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
 
 
@@ -681,13 +681,12 @@
 
   recovery_two_step_img_name = "recovery-two-step.img"
   recovery_two_step_img_path = os.path.join(
-      OPTIONS.input_tmp, "IMAGES", recovery_two_step_img_name)
+      OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
   if os.path.exists(recovery_two_step_img_path):
-    recovery_two_step_img = common.GetBootableImage(
-        recovery_two_step_img_name, recovery_two_step_img_name,
-        OPTIONS.input_tmp, "RECOVERY")
-    common.ZipWriteStr(
-        output_zip, recovery_two_step_img_name, recovery_two_step_img.data)
+    common.ZipWrite(
+        output_zip,
+        recovery_two_step_img_path,
+        arcname=recovery_two_step_img_name)
     logger.info(
         "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
     script.WriteRawImage("/boot", recovery_two_step_img_name)
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 4cb3a37..3119afa 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -111,6 +111,7 @@
 import copy
 import errno
 import gzip
+import io
 import itertools
 import logging
 import os
@@ -422,7 +423,8 @@
     if filename.startswith("IMAGES/"):
       continue
 
-    # Skip split super images, which will be re-generated during signing.
+    # Skip OTA-specific images (e.g. split super images), which will be
+    # re-generated during signing.
     if filename.startswith("OTA/") and filename.endswith(".img"):
       continue
 
@@ -746,12 +748,7 @@
     filename: The archive name in the output zip.
     keys: A list of public keys to use during OTA package verification.
   """
-
-  try:
-    from StringIO import StringIO
-  except ImportError:
-    from io import StringIO
-  temp_file = StringIO()
+  temp_file = io.BytesIO()
   certs_zip = zipfile.ZipFile(temp_file, "w")
   for k in keys:
     common.ZipWrite(certs_zip, k)
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 3a2198c..bcfb1c1 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1074,6 +1074,69 @@
       self.assertRaises(
           AssertionError, common.LoadInfoDict, target_files_zip, True)
 
+  def test_MergeDynamicPartitionInfoDicts_ReturnsMergedDict(self):
+    framework_dict = {
+        'super_partition_groups': 'group_a',
+        'dynamic_partition_list': 'system',
+        'super_group_a_list': 'system',
+    }
+    vendor_dict = {
+        'super_partition_groups': 'group_a group_b',
+        'dynamic_partition_list': 'vendor product',
+        'super_group_a_list': 'vendor',
+        'super_group_a_size': '1000',
+        'super_group_b_list': 'product',
+        'super_group_b_size': '2000',
+    }
+    merged_dict = common.MergeDynamicPartitionInfoDicts(
+        framework_dict=framework_dict,
+        vendor_dict=vendor_dict,
+        size_prefix='super_',
+        size_suffix='_size',
+        list_prefix='super_',
+        list_suffix='_list')
+    expected_merged_dict = {
+        'super_partition_groups': 'group_a group_b',
+        'dynamic_partition_list': 'system vendor product',
+        'super_group_a_list': 'system vendor',
+        'super_group_a_size': '1000',
+        'super_group_b_list': 'product',
+        'super_group_b_size': '2000',
+    }
+    self.assertEqual(merged_dict, expected_merged_dict)
+
+  def test_MergeDynamicPartitionInfoDicts_IgnoringFrameworkGroupSize(self):
+    framework_dict = {
+        'super_partition_groups': 'group_a',
+        'dynamic_partition_list': 'system',
+        'super_group_a_list': 'system',
+        'super_group_a_size': '5000',
+    }
+    vendor_dict = {
+        'super_partition_groups': 'group_a group_b',
+        'dynamic_partition_list': 'vendor product',
+        'super_group_a_list': 'vendor',
+        'super_group_a_size': '1000',
+        'super_group_b_list': 'product',
+        'super_group_b_size': '2000',
+    }
+    merged_dict = common.MergeDynamicPartitionInfoDicts(
+        framework_dict=framework_dict,
+        vendor_dict=vendor_dict,
+        size_prefix='super_',
+        size_suffix='_size',
+        list_prefix='super_',
+        list_suffix='_list')
+    expected_merged_dict = {
+        'super_partition_groups': 'group_a group_b',
+        'dynamic_partition_list': 'system vendor product',
+        'super_group_a_list': 'system vendor',
+        'super_group_a_size': '1000',
+        'super_group_b_list': 'product',
+        'super_group_b_size': '2000',
+    }
+    self.assertEqual(merged_dict, expected_merged_dict)
+
 
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.
diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py
index 1b1c725..1abe83c 100644
--- a/tools/releasetools/test_merge_target_files.py
+++ b/tools/releasetools/test_merge_target_files.py
@@ -22,7 +22,6 @@
                                 DEFAULT_FRAMEWORK_ITEM_LIST,
                                 DEFAULT_VENDOR_ITEM_LIST,
                                 DEFAULT_FRAMEWORK_MISC_INFO_KEYS, copy_items,
-                                merge_dynamic_partition_info_dicts,
                                 process_apex_keys_apk_certs_common)
 
 
@@ -126,69 +125,6 @@
                                 framework_misc_info_keys,
                                 DEFAULT_VENDOR_ITEM_LIST))
 
-  def test_merge_dynamic_partition_info_dicts_ReturnsMergedDict(self):
-    framework_dict = {
-        'super_partition_groups': 'group_a',
-        'dynamic_partition_list': 'system',
-        'super_group_a_list': 'system',
-    }
-    vendor_dict = {
-        'super_partition_groups': 'group_a group_b',
-        'dynamic_partition_list': 'vendor product',
-        'super_group_a_list': 'vendor',
-        'super_group_a_size': '1000',
-        'super_group_b_list': 'product',
-        'super_group_b_size': '2000',
-    }
-    merged_dict = merge_dynamic_partition_info_dicts(
-        framework_dict=framework_dict,
-        vendor_dict=vendor_dict,
-        size_prefix='super_',
-        size_suffix='_size',
-        list_prefix='super_',
-        list_suffix='_list')
-    expected_merged_dict = {
-        'super_partition_groups': 'group_a group_b',
-        'dynamic_partition_list': 'system vendor product',
-        'super_group_a_list': 'system vendor',
-        'super_group_a_size': '1000',
-        'super_group_b_list': 'product',
-        'super_group_b_size': '2000',
-    }
-    self.assertEqual(merged_dict, expected_merged_dict)
-
-  def test_merge_dynamic_partition_info_dicts_IgnoringFrameworkGroupSize(self):
-    framework_dict = {
-        'super_partition_groups': 'group_a',
-        'dynamic_partition_list': 'system',
-        'super_group_a_list': 'system',
-        'super_group_a_size': '5000',
-    }
-    vendor_dict = {
-        'super_partition_groups': 'group_a group_b',
-        'dynamic_partition_list': 'vendor product',
-        'super_group_a_list': 'vendor',
-        'super_group_a_size': '1000',
-        'super_group_b_list': 'product',
-        'super_group_b_size': '2000',
-    }
-    merged_dict = merge_dynamic_partition_info_dicts(
-        framework_dict=framework_dict,
-        vendor_dict=vendor_dict,
-        size_prefix='super_',
-        size_suffix='_size',
-        list_prefix='super_',
-        list_suffix='_list')
-    expected_merged_dict = {
-        'super_partition_groups': 'group_a group_b',
-        'dynamic_partition_list': 'system vendor product',
-        'super_group_a_list': 'system vendor',
-        'super_group_a_size': '1000',
-        'super_group_b_list': 'product',
-        'super_group_b_size': '2000',
-    }
-    self.assertEqual(merged_dict, expected_merged_dict)
-
   def test_process_apex_keys_apk_certs_ReturnsTrueIfNoConflicts(self):
     output_dir = common.MakeTempDir()
     os.makedirs(os.path.join(output_dir, 'META'))
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 0100729..70c147e 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -15,6 +15,7 @@
 #
 
 import base64
+import io
 import os.path
 import zipfile
 
@@ -22,7 +23,7 @@
 import test_utils
 from sign_target_files_apks import (
     CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ReadApexKeysInfo,
-    ReplaceCerts, ReplaceVerityKeyId, RewriteProps)
+    ReplaceCerts, ReplaceVerityKeyId, RewriteProps, WriteOtacerts)
 
 
 class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
@@ -236,6 +237,22 @@
     }
     self.assertEqual(output_xml, ReplaceCerts(input_xml))
 
+  def test_WriteOtacerts(self):
+    certs = [
+        os.path.join(self.testdata_dir, 'platform.x509.pem'),
+        os.path.join(self.testdata_dir, 'media.x509.pem'),
+        os.path.join(self.testdata_dir, 'testkey.x509.pem'),
+    ]
+    entry_name = 'SYSTEM/etc/security/otacerts.zip'
+    output_file = common.MakeTempFile(suffix='.zip')
+    with zipfile.ZipFile(output_file, 'w') as output_zip:
+      WriteOtacerts(output_zip, entry_name, certs)
+    with zipfile.ZipFile(output_file) as input_zip:
+      self.assertIn(entry_name, input_zip.namelist())
+      otacerts_file = io.BytesIO(input_zip.read(entry_name))
+      with zipfile.ZipFile(otacerts_file) as otacerts_zip:
+        self.assertEqual(3, len(otacerts_zip.namelist()))
+
   def test_CheckApkAndApexKeysAvailable(self):
     input_file = common.MakeTempFile(suffix='.zip')
     with zipfile.ZipFile(input_file, 'w') as input_zip:
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index 435e7f2..d189499 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -257,7 +257,10 @@
     if verity_key is None:
       verity_key = info_dict['verity_key'] + '.x509.pem'
     for image in ('boot.img', 'recovery.img', 'recovery-two-step.img'):
-      image_path = os.path.join(input_tmp, 'IMAGES', image)
+      if image == 'recovery-two-step.img':
+        image_path = os.path.join(input_tmp, 'OTA', image)
+      else:
+        image_path = os.path.join(input_tmp, 'IMAGES', image)
       if not os.path.exists(image_path):
         continue