Merge "Replace PRODUCT_BOOT_JARS with DEXPREOPT_BOOT_JARS_MODULES for nopreloads"
diff --git a/core/Makefile b/core/Makefile
index af9e935..646aaec 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -833,6 +833,16 @@
 
 $(call dist-for-goals, sdk win_sdk sdk_addon, $(INSTALLED_FILES_FILE_ROOT))
 
+#------------------------------------------------------------------
+# dtb
+
+ifdef BOARD_PREBUILT_DTBIMAGE_DIR
+INSTALLED_DTBIMAGE_TARGET := $(PRODUCT_OUT)/dtb.img
+$(INSTALLED_DTBIMAGE_TARGET) : $(addprefix $(BOARD_PREBUILT_DTBIMAGE_DIR)/,\
+	$(call find-files-in-subdirs,$(BOARD_PREBUILT_DTBIMAGE_DIR),*.dtb,.))
+	cat $^ > $@
+endif
+
 # -----------------------------------------------------------------
 # the ramdisk
 ifdef BUILDING_RAMDISK_IMAGE
@@ -877,6 +887,10 @@
 	$(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
 	--kernel $(INSTALLED_KERNEL_TARGET)
 
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+  INTERNAL_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+endif
+
 ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
 INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
 endif
@@ -1409,6 +1423,12 @@
         $(hide) echo "avb_system_key_path=$(BOARD_AVB_SYSTEM_KEY_PATH)" >> $(1)
         $(hide) echo "avb_system_algorithm=$(BOARD_AVB_SYSTEM_ALGORITHM)" >> $(1)
         $(hide) echo "avb_system_rollback_index_location=$(BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION)" >> $(1)))
+$(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_system_other_hashtree_enable=$(BOARD_AVB_ENABLE)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_system_other_add_hashtree_footer_args=$(BOARD_AVB_SYSTEM_OTHER_ADD_HASHTREE_FOOTER_ARGS)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),\
+    $(if $(BOARD_AVB_SYSTEM_OTHER_KEY_PATH),\
+        $(hide) echo "avb_system_other_key_path=$(BOARD_AVB_SYSTEM_OTHER_KEY_PATH)" >> $(1)
+        $(hide) echo "avb_system_other_algorithm=$(BOARD_AVB_SYSTEM_OTHER_ALGORITHM)" >> $(1)))
 $(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_vendor_hashtree_enable=$(BOARD_AVB_ENABLE)" >> $(1))
 $(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_vendor_add_hashtree_footer_args=$(BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS)" >> $(1))
 $(if $(BOARD_AVB_ENABLE),\
@@ -1777,6 +1797,9 @@
 ifdef BOARD_INCLUDE_RECOVERY_ACPIO
   INTERNAL_RECOVERYIMAGE_ARGS += --recovery_acpio $(BOARD_RECOVERY_ACPIO)
 endif
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+  INTERNAL_RECOVERYIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+endif
 
 # Keys authorized to sign OTA packages this build will accept.  The
 # build always uses dev-keys for this; release packaging tools will
@@ -1866,6 +1889,9 @@
 ifdef BOARD_INCLUDE_RECOVERY_ACPIO
 $(INSTALLED_BOOTIMAGE_TARGET): $(BOARD_RECOVERY_ACPIO)
 endif
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+$(INSTALLED_BOOTIMAGE_TARGET): $(INSTALLED_DTBIMAGE_TARGET)
+endif
 
 $(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
 	    $(INTERNAL_ROOT_FILES) \
@@ -1889,6 +1915,9 @@
 ifdef BOARD_INCLUDE_RECOVERY_ACPIO
 $(INSTALLED_RECOVERYIMAGE_TARGET): $(BOARD_RECOVERY_ACPIO)
 endif
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+$(INSTALLED_RECOVERYIMAGE_TARGET): $(INSTALLED_DTBIMAGE_TARGET)
+endif
 
 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
 	    $(INTERNAL_ROOT_FILES) \
@@ -2811,6 +2840,23 @@
 BOARD_AVB_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
 endif
 
+# AVB signing for system_other.img.
+ifdef BUILDING_SYSTEM_OTHER_IMAGE
+ifdef BOARD_AVB_SYSTEM_OTHER_KEY_PATH
+$(if $(BOARD_AVB_SYSTEM_OTHER_ALGORITHM),,$(error BOARD_AVB_SYSTEM_OTHER_ALGORITHM is not defined))
+else
+# If key path isn't specified, use the same key as BOARD_AVB_KEY_PATH.
+BOARD_AVB_SYSTEM_OTHER_KEY_PATH := $(BOARD_AVB_KEY_PATH)
+BOARD_AVB_SYSTEM_OTHER_ALGORITHM := $(BOARD_AVB_ALGORITHM)
+endif
+
+ifndef BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX
+BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
+endif
+
+BOARD_AVB_SYSTEM_OTHER_ADD_HASHTREE_FOOTER_ARGS += --rollback_index $(BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX)
+endif # end of AVB for BUILDING_SYSTEM_OTHER_IMAGE
+
 INTERNAL_AVB_PARTITIONS_IN_CHAINED_VBMETA_IMAGES := \
     $(BOARD_AVB_VBMETA_SYSTEM) \
     $(BOARD_AVB_VBMETA_VENDOR)
@@ -3500,6 +3546,9 @@
 ifdef BOARD_INCLUDE_RECOVERY_ACPIO
 	$(hide) cp $(BOARD_RECOVERY_ACPIO) $(zip_root)/$(PRIVATE_RECOVERY_OUT)/recovery_acpio
 endif
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+	$(hide) cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/$(PRIVATE_RECOVERY_OUT)/dtb
+endif
 ifdef INTERNAL_KERNEL_CMDLINE
 	$(hide) echo "$(INTERNAL_KERNEL_CMDLINE)" > $(zip_root)/$(PRIVATE_RECOVERY_OUT)/cmdline
 endif
@@ -3527,6 +3576,9 @@
 ifdef INSTALLED_2NDBOOTLOADER_TARGET
 	$(hide) cp $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second
 endif
+ifdef INSTALLED_DTBIMAGE_TARGET
+	$(hide) cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/BOOT/dtb
+endif
 ifdef INTERNAL_KERNEL_CMDLINE
 	$(hide) echo "$(INTERNAL_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline
 endif
diff --git a/core/check_elf_file.mk b/core/check_elf_file.mk
new file mode 100644
index 0000000..69d891c
--- /dev/null
+++ b/core/check_elf_file.mk
@@ -0,0 +1,45 @@
+# Check the correctness of the prebuilt ELF files
+#
+# This check ensures that DT_SONAME matches with the filename, DT_NEEDED
+# matches the shared libraries specified in LOCAL_SHARED_LIBRARIES, and all
+# undefined symbols in the prebuilt binary can be found in one of the shared
+# libraries specified in LOCAL_SHARED_LIBRARIES.
+#
+# Inputs:
+# - LOCAL_ALLOW_UNDEFINED_SYMBOLS
+# - LOCAL_BUILT_MODULE
+# - LOCAL_IS_HOST_MODULE
+# - LOCAL_MODULE_CLASS
+# - intermediates
+# - my_installed_module_stem
+# - my_prebuilt_src_file
+
+ifndef LOCAL_IS_HOST_MODULE
+ifneq ($(filter $(LOCAL_MODULE_CLASS),SHARED_LIBRARIES EXECUTABLES NATIVE_TESTS),)
+check_elf_files_stamp := $(intermediates)/check_elf_files.timestamp
+$(check_elf_files_stamp): PRIVATE_SONAME := $(if $(filter $(LOCAL_MODULE_CLASS),SHARED_LIBRARIES),$(my_installed_module_stem))
+$(check_elf_files_stamp): PRIVATE_ALLOW_UNDEFINED_SYMBOLS := $(LOCAL_ALLOW_UNDEFINED_SYMBOLS)
+$(check_elf_files_stamp): PRIVATE_SHARED_LIBRARY_FILES := # This variable will be set by `core/main.mk`
+$(check_elf_files_stamp): $(my_prebuilt_src_file) $(CHECK_ELF_FILE) $(LLVM_READOBJ)
+	@echo Check prebuilt ELF binary: $<
+	$(hide) mkdir -p $(dir $@)
+	$(hide) rm -f $@
+	$(hide) $(CHECK_ELF_FILE) \
+	    --skip-bad-elf-magic \
+	    --skip-unknown-elf-machine \
+	    $(if $(PRIVATE_SONAME),--soname $(PRIVATE_SONAME)) \
+	    $(foreach l,$(PRIVATE_SHARED_LIBRARY_FILES),--shared-lib $(l)) \
+	    $(if $(PRIVATE_ALLOW_UNDEFINED_SYMBOLS),--allow-undefined-symbols) \
+	    --llvm-readobj=$(LLVM_READOBJ) \
+	    $<
+	$(hide) touch $@
+
+ifneq ($(PRODUCT_CHECK_ELF_FILES)$(CHECK_ELF_FILES),)
+ifneq ($(LOCAL_CHECK_ELF_FILES),false)
+$(LOCAL_BUILT_MODULE): $(check_elf_files_stamp)
+check-elf-files: $(check_elf_files_stamp)
+endif  # LOCAL_CHECK_ELF_FILES
+endif  # PRODUCT_CHECK_ELF_FILES or CHECK_ELF_FILES
+
+endif  # SHARED_LIBRARIES, EXECUTABLES, NATIVE_TESTS
+endif  # !LOCAL_IS_HOST_MODULE
diff --git a/core/clang/config.mk b/core/clang/config.mk
index ca3a1fa..063fd34 100644
--- a/core/clang/config.mk
+++ b/core/clang/config.mk
@@ -1,5 +1,7 @@
 ## Clang configurations.
 
+LLVM_READOBJ := $(LLVM_PREBUILTS_BASE)/$(BUILD_OS)-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-readobj
+
 LLVM_RTLIB_PATH := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib64/clang/$(LLVM_RELEASE_VERSION)/lib/linux/
 
 define convert-to-clang-flags
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index f9e07f8..e190916 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -304,6 +304,7 @@
 LOCAL_WHOLE_STATIC_LIBRARIES:=
 LOCAL_XOM:=
 LOCAL_YACCFLAGS:=
+LOCAL_CHECK_ELF_FILES:=
 # TODO: deprecate, it does nothing
 OVERRIDE_BUILT_MODULE_PATH:=
 
diff --git a/core/config.mk b/core/config.mk
index b0805fc..486193d 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -707,6 +707,7 @@
 DATA_BINDING_COMPILER := $(HOST_OUT_JAVA_LIBRARIES)/databinding-compiler.jar
 FAT16COPY := build/make/tools/fat16copy.py
 CHECK_LINK_TYPE := build/make/tools/check_link_type.py
+CHECK_ELF_FILE := build/make/tools/check_elf_file.py
 LPMAKE := $(HOST_OUT_EXECUTABLES)/lpmake$(HOST_EXECUTABLE_SUFFIX)
 BUILD_SUPER_IMAGE := build/make/tools/releasetools/build_super_image.py
 
diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk
index 01c98e1..60fd8db 100644
--- a/core/dex_preopt.mk
+++ b/core/dex_preopt.mk
@@ -76,9 +76,7 @@
 $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS): $(HIDDENAPI) $(HIDDENAPI_STUBS) \
                                            $(HIDDENAPI_STUBS_SYSTEM) $(HIDDENAPI_STUBS_TEST) \
                                            $(HIDDENAPI_STUBS_CORE_PLATFORM)
-	for INPUT_DEX in $(PRIVATE_DEX_INPUTS); do \
-		find `dirname $${INPUT_DEX}` -maxdepth 1 -name "classes*.dex"; \
-	done | sort | sed 's/^/--boot-dex=/' | xargs $(HIDDENAPI) list \
+	$(HIDDENAPI) list $(addprefix --boot-dex=,$(PRIVATE_DEX_INPUTS)) \
 	    --public-stub-classpath=$(call normalize-path-list, $(PRIVATE_HIDDENAPI_STUBS)) \
 	    --public-stub-classpath=$(call normalize-path-list, $(PRIVATE_HIDDENAPI_STUBS_SYSTEM)) \
 	    --public-stub-classpath=$(call normalize-path-list, $(PRIVATE_HIDDENAPI_STUBS_TEST)) \
diff --git a/core/dex_preopt_config.mk b/core/dex_preopt_config.mk
index d1236f5..90bdaf3 100644
--- a/core/dex_preopt_config.mk
+++ b/core/dex_preopt_config.mk
@@ -9,7 +9,8 @@
 
 DEXPREOPT_BOOT_JARS_MODULES := $(NON_UPDATABLE_BOOT_MODULES)
 DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS := $(NON_UPDATABLE_BOOT_LOCATIONS)
-DEXPREOPT_BOOTCLASSPATH_DEX_FILES := $(foreach jar,$(DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS),$(PRODUCT_OUT)$(jar))
+DEXPREOPT_BOOT_JARS_INPUT_PATH := $(PRODUCT_OUT)/dex_bootjars_input
+DEXPREOPT_BOOTCLASSPATH_DEX_FILES := $(foreach m,$(NON_UPDATABLE_BOOT_MODULES),$(DEXPREOPT_BOOT_JARS_INPUT_PATH)/$(m).jar)
 
 # Create paths for boot image.
 DEXPREOPT_BUILD_DIR := $(OUT_DIR)
diff --git a/core/dex_preopt_libart.mk b/core/dex_preopt_libart.mk
index 9d320b9..de7dcdd 100644
--- a/core/dex_preopt_libart.mk
+++ b/core/dex_preopt_libart.mk
@@ -10,6 +10,9 @@
 LIBART_TARGET_BOOT_DEX_LOCATIONS := $(DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS)
 LIBART_TARGET_BOOT_DEX_FILES := $(foreach mod,$(NON_UPDATABLE_BOOT_MODULES),$(call intermediates-dir-for,JAVA_LIBRARIES,$(mod),,COMMON)/javalib.jar)
 
+# Copy the files to a location Soong dex preopt will look at.
+$(foreach mod,$(LIBART_TARGET_BOOT_JARS),$(eval $(call copy-one-file,$(call intermediates-dir-for,JAVA_LIBRARIES,$(mod),,COMMON)/javalib.jar,$(DEXPREOPT_BOOT_JARS_INPUT_PATH)/$(mod).jar)))
+
 # dex preopt on the bootclasspath produces multiple files.  The first dex file
 # is converted into to boot.art (to match the legacy assumption that boot.art
 # exists), and the rest are converted to boot-<name>.art.
diff --git a/core/java_common.mk b/core/java_common.mk
index 4e331d0..f5da120 100644
--- a/core/java_common.mk
+++ b/core/java_common.mk
@@ -386,6 +386,7 @@
   endif # USE_CORE_LIB_BOOTCLASSPATH
 endif # !LOCAL_IS_HOST_MODULE
 
+ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) $(full_java_bootclasspath_libs)
 
 # Export the SDK libs. The sdk library names listed in LOCAL_SDK_LIBRARIES are first exported.
 # Then sdk library names exported from dependencies are all re-exported.
diff --git a/core/main.mk b/core/main.mk
index c84cbe0..96b2132 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -786,9 +786,43 @@
 $(call resolve-shared-libs-depes,HOST_CROSS_,,true)
 endif
 
+# Pass the shared libraries dependencies to prebuilt ELF file check.
+define add-elf-file-check-shared-lib
+$(1): PRIVATE_SHARED_LIBRARY_FILES += $(2)
+$(1): $(2)
+endef
+
+define resolve-shared-libs-for-elf-file-check
+$(foreach m,$($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))$(1)DEPENDENCIES_ON_SHARED_LIBRARIES),\
+  $(eval p := $(subst :,$(space),$(m)))\
+  $(eval mod := $(firstword $(p)))\
+  \
+  $(eval deps := $(subst $(comma),$(space),$(lastword $(p))))\
+  $(if $(2),$(eval deps := $(addsuffix $($(1)2ND_ARCH_MODULE_SUFFIX),$(deps))))\
+  $(eval root := $(1)OUT$(if $(call streq,$(1),TARGET_),_ROOT))\
+  $(eval deps := $(filter $($(root))/%$($(1)SHLIB_SUFFIX),$(call module-built-files,$(deps))))\
+  \
+  $(eval r := $(firstword $(filter \
+    $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/EXECUTABLES/%\
+    $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/NATIVE_TESTS/%\
+    $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/SHARED_LIBRARIES/%,\
+    $(call module-built-files,$(mod)))))\
+  \
+  $(if $(r),\
+    $(eval stamp := $(dir $(r))check_elf_files.timestamp)\
+    $(eval $(call add-elf-file-check-shared-lib,$(stamp),$(deps)))\
+  ))
+endef
+
+$(call resolve-shared-libs-for-elf-file-check,TARGET_)
+ifdef TARGET_2ND_ARCH
+$(call resolve-shared-libs-for-elf-file-check,TARGET_,true)
+endif
+
 m :=
 r :=
 p :=
+stamp :=
 deps :=
 add-required-deps :=
 
@@ -1085,8 +1119,8 @@
   # Verify the artifact path requirements made by included products.
   is_asan := $(if $(filter address,$(SANITIZE_TARGET)),true)
   ifneq (true,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS)))
-  # Fakes don't get installed, and host files are irrelevant.
-  static_whitelist_patterns := $(TARGET_OUT_FAKE)/% $(HOST_OUT)/%
+  # Fakes don't get installed, host files are irrelevant, and NDK stubs aren't installed to device.
+  static_whitelist_patterns := $(TARGET_OUT_FAKE)/% $(HOST_OUT)/% $(SOONG_OUT_DIR)/ndk/%
   # RROs become REQUIRED by the source module, but are always placed on the vendor partition.
   static_whitelist_patterns += %__auto_generated_rro.apk
   ifeq (true,$(BOARD_USES_SYSTEM_OTHER_ODEX))
@@ -1529,6 +1563,9 @@
 .PHONY: findlsdumps
 findlsdumps: $(FIND_LSDUMPS_FILE)
 
+.PHONY: check-elf-files
+check-elf-files:
+
 #xxx scrape this from ALL_MODULE_NAME_TAGS
 .PHONY: modules
 modules:
diff --git a/core/prebuilt_internal.mk b/core/prebuilt_internal.mk
index 9b995e2..0b48a77 100644
--- a/core/prebuilt_internal.mk
+++ b/core/prebuilt_internal.mk
@@ -168,6 +168,9 @@
 include $(BUILD_SYSTEM)/link_type.mk
 endif  # prebuilt_module_is_a_library
 
+# Check prebuilt ELF binaries.
+include $(BUILD_SYSTEM)/check_elf_file.mk
+
 # The real dependency will be added after all Android.mks are loaded and the install paths
 # of the shared libraries are determined.
 ifdef LOCAL_INSTALLED_MODULE
diff --git a/core/product.mk b/core/product.mk
index 0219ce4..c3cbb0c 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -227,6 +227,7 @@
     PRODUCT_BUILD_USERDATA_IMAGE \
     PRODUCT_UPDATABLE_BOOT_MODULES \
     PRODUCT_UPDATABLE_BOOT_LOCATIONS \
+    PRODUCT_CHECK_ELF_FILES \
 
 define dump-product
 $(info ==== $(1) ====)\
diff --git a/core/product_config.mk b/core/product_config.mk
index 27e8ecf..1f44692 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -477,6 +477,11 @@
 PRODUCT_ENFORCE_RRO_TARGETS := \
     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_ENFORCE_RRO_TARGETS))
 
+# Whether the product would like to check prebuilt ELF files.
+PRODUCT_CHECK_ELF_FILES := \
+    $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_CHECK_ELF_FILES))
+.KATI_READONLY := PRODUCT_CHECK_ELF_FILES
+
 # Add reserved headroom to a system image.
 PRODUCT_SYSTEM_HEADROOM := \
     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_HEADROOM))
diff --git a/core/soong_cc_prebuilt.mk b/core/soong_cc_prebuilt.mk
index 088b076..39cc0f0 100644
--- a/core/soong_cc_prebuilt.mk
+++ b/core/soong_cc_prebuilt.mk
@@ -93,6 +93,12 @@
   name_without_suffix :=
 endif
 
+# Check prebuilt ELF binaries.
+ifneq ($(LOCAL_CHECK_ELF_FILES),)
+my_prebuilt_src_file := $(LOCAL_PREBUILT_MODULE_FILE)
+include $(BUILD_SYSTEM)/check_elf_file.mk
+endif
+
 # The real dependency will be added after all Android.mks are loaded and the install paths
 # of the shared libraries are determined.
 ifdef LOCAL_INSTALLED_MODULE
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 5201a62..cd8cd7f 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -123,6 +123,8 @@
 $(call add_json_bool, Treble_linker_namespaces,          $(filter true,$(PRODUCT_TREBLE_LINKER_NAMESPACES)))
 $(call add_json_bool, Enforce_vintf_manifest,            $(filter true,$(PRODUCT_ENFORCE_VINTF_MANIFEST)))
 
+$(call add_json_bool, Check_elf_files,                   $(filter true,$(PRODUCT_CHECK_ELF_FILES)))
+
 $(call add_json_bool, Uml,                               $(filter true,$(TARGET_USER_MODE_LINUX)))
 $(call add_json_bool, Use_lmkd_stats_log,                $(filter true,$(TARGET_LMKD_STATS_LOG)))
 $(call add_json_str,  VendorPath,                        $(TARGET_COPY_OUT_VENDOR))
diff --git a/target/board/BoardConfigGsiCommon.mk b/target/board/BoardConfigGsiCommon.mk
index 05ce35f..1037eec 100644
--- a/target/board/BoardConfigGsiCommon.mk
+++ b/target/board/BoardConfigGsiCommon.mk
@@ -58,3 +58,6 @@
 # The partition size doesn't matter, just to make build pass.
 BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
 BOARD_CACHEIMAGE_PARTITION_SIZE := 16777216
+
+# Disable 64 bit mediadrmserver
+TARGET_ENABLE_MEDIADRM_64 :=
diff --git a/target/board/BoardConfigMainlineCommon.mk b/target/board/BoardConfigMainlineCommon.mk
index 46e5d93..9a4fb3a 100644
--- a/target/board/BoardConfigMainlineCommon.mk
+++ b/target/board/BoardConfigMainlineCommon.mk
@@ -21,6 +21,9 @@
 # Required flag for non-64 bit devices from P.
 TARGET_USES_64_BIT_BINDER := true
 
+# 64 bit mediadrmserver
+TARGET_ENABLE_MEDIADRM_64 := true
+
 # Puts odex files on system_other, as well as causing dex files not to get
 # stripped from APKs.
 BOARD_USES_SYSTEM_OTHER_ODEX := true
diff --git a/target/product/base_product.mk b/target/product/base_product.mk
index 6531304..bd53fb1 100644
--- a/target/product/base_product.mk
+++ b/target/product/base_product.mk
@@ -17,3 +17,4 @@
 # Base modules and settings for the product partition.
 PRODUCT_PACKAGES += \
     healthd \
+    ModuleMetadata \
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index a220188..4a3ae4d 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -208,7 +208,6 @@
     MediaProvider \
     mediaserver \
     mke2fs \
-    ModuleMetadata \
     monkey \
     mtpd \
     ndc \
diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py
new file mode 100755
index 0000000..38c1cf4
--- /dev/null
+++ b/tools/check_elf_file.py
@@ -0,0 +1,538 @@
+#!/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.
+
+"""ELF file checker.
+
+This command ensures all undefined symbols in an ELF file can be resolved to
+global (or weak) symbols defined in shared objects specified in DT_NEEDED
+entries.
+"""
+
+from __future__ import print_function
+
+import argparse
+import collections
+import os
+import os.path
+import re
+import struct
+import subprocess
+import sys
+
+
+_ELF_MAGIC = b'\x7fELF'
+
+
+# Known machines
+_EM_386 = 3
+_EM_ARM = 40
+_EM_X86_64 = 62
+_EM_AARCH64 = 183
+
+_KNOWN_MACHINES = {_EM_386, _EM_ARM, _EM_X86_64, _EM_AARCH64}
+
+
+# ELF header struct
+_ELF_HEADER_STRUCT = (
+  ('ei_magic', '4s'),
+  ('ei_class', 'B'),
+  ('ei_data', 'B'),
+  ('ei_version', 'B'),
+  ('ei_osabi', 'B'),
+  ('ei_pad', '8s'),
+  ('e_type', 'H'),
+  ('e_machine', 'H'),
+  ('e_version', 'I'),
+)
+
+_ELF_HEADER_STRUCT_FMT = ''.join(_fmt for _, _fmt in _ELF_HEADER_STRUCT)
+
+
+ELFHeader = collections.namedtuple(
+  'ELFHeader', [_name for _name, _ in _ELF_HEADER_STRUCT])
+
+
+ELF = collections.namedtuple(
+  'ELF',
+  ('dt_soname', 'dt_needed', 'imported', 'exported', 'header'))
+
+
+def _get_os_name():
+  """Get the host OS name."""
+  if sys.platform == 'linux2':
+    return 'linux'
+  if sys.platform == 'darwin':
+    return 'darwin'
+  raise ValueError(sys.platform + ' is not supported')
+
+
+def _get_build_top():
+  """Find the build top of the source tree ($ANDROID_BUILD_TOP)."""
+  prev_path = None
+  curr_path = os.path.abspath(os.getcwd())
+  while prev_path != curr_path:
+    if os.path.exists(os.path.join(curr_path, '.repo')):
+      return curr_path
+    prev_path = curr_path
+    curr_path = os.path.dirname(curr_path)
+  return None
+
+
+def _select_latest_llvm_version(versions):
+  """Select the latest LLVM prebuilts version from a set of versions."""
+  pattern = re.compile('clang-r([0-9]+)([a-z]?)')
+  found_rev = 0
+  found_ver = None
+  for curr_ver in versions:
+    match = pattern.match(curr_ver)
+    if not match:
+      continue
+    curr_rev = int(match.group(1))
+    if not found_ver or curr_rev > found_rev or (
+        curr_rev == found_rev and curr_ver > found_ver):
+      found_rev = curr_rev
+      found_ver = curr_ver
+  return found_ver
+
+
+def _get_latest_llvm_version(llvm_dir):
+  """Find the latest LLVM prebuilts version from `llvm_dir`."""
+  return _select_latest_llvm_version(os.listdir(llvm_dir))
+
+
+def _get_llvm_dir():
+  """Find the path to LLVM prebuilts."""
+  build_top = _get_build_top()
+
+  llvm_prebuilts_base = os.environ.get('LLVM_PREBUILTS_BASE')
+  if not llvm_prebuilts_base:
+    llvm_prebuilts_base = os.path.join('prebuilts', 'clang', 'host')
+
+  llvm_dir = os.path.join(
+    build_top, llvm_prebuilts_base, _get_os_name() + '-x86')
+
+  if not os.path.exists(llvm_dir):
+    return None
+
+  llvm_prebuilts_version = os.environ.get('LLVM_PREBUILTS_VERSION')
+  if not llvm_prebuilts_version:
+    llvm_prebuilts_version = _get_latest_llvm_version(llvm_dir)
+
+  llvm_dir = os.path.join(llvm_dir, llvm_prebuilts_version)
+
+  if not os.path.exists(llvm_dir):
+    return None
+
+  return llvm_dir
+
+
+def _get_llvm_readobj():
+  """Find the path to llvm-readobj executable."""
+  llvm_dir = _get_llvm_dir()
+  llvm_readobj = os.path.join(llvm_dir, 'bin', 'llvm-readobj')
+  return llvm_readobj if os.path.exists(llvm_readobj) else 'llvm-readobj'
+
+
+class ELFError(ValueError):
+  """Generic ELF parse error"""
+  pass
+
+
+class ELFInvalidMagicError(ELFError):
+  """Invalid ELF magic word error"""
+  def __init__(self):
+    super(ELFInvalidMagicError, self).__init__('bad ELF magic')
+
+
+class ELFParser(object):
+  """ELF file parser"""
+
+  @classmethod
+  def _read_elf_header(cls, elf_file_path):
+    """Read the ELF magic word from the beginning of the file."""
+    with open(elf_file_path, 'rb') as elf_file:
+      buf = elf_file.read(struct.calcsize(_ELF_HEADER_STRUCT_FMT))
+      try:
+        return ELFHeader(*struct.unpack(_ELF_HEADER_STRUCT_FMT, buf))
+      except struct.error:
+        return None
+
+
+  @classmethod
+  def open(cls, elf_file_path, llvm_readobj):
+    """Open and parse the ELF file."""
+    # Parse the ELF header for simple sanity checks.
+    header = cls._read_elf_header(elf_file_path)
+    if not header or header.ei_magic != _ELF_MAGIC:
+      raise ELFInvalidMagicError()
+
+    # Run llvm-readobj and parse the output.
+    return cls._read_llvm_readobj(elf_file_path, header, llvm_readobj)
+
+
+  @classmethod
+  def _find_prefix(cls, pattern, lines_it):
+    """Iterate `lines_it` until finding a string that starts with `pattern`."""
+    for line in lines_it:
+      if line.startswith(pattern):
+        return True
+    return False
+
+
+  @classmethod
+  def _read_llvm_readobj(cls, elf_file_path, header, llvm_readobj):
+    """Run llvm-readobj and parse the output."""
+    proc = subprocess.Popen(
+      [llvm_readobj, '-dynamic-table', '-dyn-symbols', elf_file_path],
+      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, _ = proc.communicate()
+    lines = out.splitlines()
+    return cls._parse_llvm_readobj(elf_file_path, header, lines)
+
+
+  @classmethod
+  def _parse_llvm_readobj(cls, elf_file_path, header, lines):
+    """Parse the output of llvm-readobj."""
+    lines_it = iter(lines)
+    imported, exported = cls._parse_dynamic_symbols(lines_it)
+    dt_soname, dt_needed = cls._parse_dynamic_table(elf_file_path, lines_it)
+    return ELF(dt_soname, dt_needed, imported, exported, header)
+
+
+  _DYNAMIC_SECTION_START_PATTERN = 'DynamicSection ['
+
+  _DYNAMIC_SECTION_NEEDED_PATTERN = re.compile(
+    '^  0x[0-9a-fA-F]+\\s+NEEDED\\s+Shared library: \\[(.*)\\]$')
+
+  _DYNAMIC_SECTION_SONAME_PATTERN = re.compile(
+    '^  0x[0-9a-fA-F]+\\s+SONAME\\s+Library soname: \\[(.*)\\]$')
+
+  _DYNAMIC_SECTION_END_PATTERN = ']'
+
+
+  @classmethod
+  def _parse_dynamic_table(cls, elf_file_path, lines_it):
+    """Parse the dynamic table section."""
+    dt_soname = os.path.basename(elf_file_path)
+    dt_needed = []
+
+    dynamic = cls._find_prefix(cls._DYNAMIC_SECTION_START_PATTERN, lines_it)
+    if not dynamic:
+      return (dt_soname, dt_needed)
+
+    for line in lines_it:
+      if line == cls._DYNAMIC_SECTION_END_PATTERN:
+        break
+
+      match = cls._DYNAMIC_SECTION_NEEDED_PATTERN.match(line)
+      if match:
+        dt_needed.append(match.group(1))
+        continue
+
+      match = cls._DYNAMIC_SECTION_SONAME_PATTERN.match(line)
+      if match:
+        dt_soname = match.group(1)
+        continue
+
+    return (dt_soname, dt_needed)
+
+
+  _DYNAMIC_SYMBOLS_START_PATTERN = 'DynamicSymbols ['
+  _DYNAMIC_SYMBOLS_END_PATTERN = ']'
+
+  _SYMBOL_ENTRY_START_PATTERN = '  Symbol {'
+  _SYMBOL_ENTRY_PATTERN = re.compile('^    ([A-Za-z0-9_]+): (.*)$')
+  _SYMBOL_ENTRY_PAREN_PATTERN = re.compile(
+    '\\s+\\((?:(?:\\d+)|(?:0x[0-9a-fA-F]+))\\)$')
+  _SYMBOL_ENTRY_END_PATTERN = '  }'
+
+
+  @classmethod
+  def _parse_symbol_name(cls, name_with_version):
+    """Split `name_with_version` into name and version. This function may split
+    at last occurrence of `@@` or `@`."""
+    name, version = name_with_version.rsplit('@', 1)
+    if name and name[-1] == '@':
+      name = name[:-1]
+    return (name, version)
+
+
+  @classmethod
+  def _parse_dynamic_symbols(cls, lines_it):
+    """Parse dynamic symbol table and collect imported and exported symbols."""
+    imported = collections.defaultdict(set)
+    exported = collections.defaultdict(set)
+
+    for symbol in cls._parse_dynamic_symbols_internal(lines_it):
+      name, version = cls._parse_symbol_name(symbol['Name'])
+      if name:
+        if symbol['Section'] == 'Undefined':
+          if symbol['Binding'] != 'Weak':
+            imported[name].add(version)
+        else:
+          if symbol['Binding'] != 'Local':
+            exported[name].add(version)
+
+    # Freeze the returned imported/exported dict.
+    return (dict(imported), dict(exported))
+
+
+  @classmethod
+  def _parse_dynamic_symbols_internal(cls, lines_it):
+    """Parse symbols entries and yield each symbols."""
+
+    if not cls._find_prefix(cls._DYNAMIC_SYMBOLS_START_PATTERN, lines_it):
+      return
+
+    for line in lines_it:
+      if line == cls._DYNAMIC_SYMBOLS_END_PATTERN:
+        return
+
+      if line == cls._SYMBOL_ENTRY_START_PATTERN:
+        symbol = {}
+        continue
+
+      if line == cls._SYMBOL_ENTRY_END_PATTERN:
+        yield symbol
+        symbol = None
+        continue
+
+      match = cls._SYMBOL_ENTRY_PATTERN.match(line)
+      if match:
+        key = match.group(1)
+        value = cls._SYMBOL_ENTRY_PAREN_PATTERN.sub('', match.group(2))
+        symbol[key] = value
+        continue
+
+
+class Checker(object):
+  """ELF file checker that checks DT_SONAME, DT_NEEDED, and symbols."""
+
+  def __init__(self, llvm_readobj):
+    self._file_path = ''
+    self._file_under_test = None
+    self._shared_libs = []
+
+    self._llvm_readobj = llvm_readobj
+
+
+  if sys.stderr.isatty():
+    _ERROR_TAG = '\033[0;1;31merror:\033[m'  # Red error
+    _NOTE_TAG = '\033[0;1;30mnote:\033[m'  # Black note
+  else:
+    _ERROR_TAG = 'error:'  # Red error
+    _NOTE_TAG = 'note:'  # Black note
+
+
+  def _error(self, *args):
+    """Emit an error to stderr."""
+    print(self._file_path + ': ' + self._ERROR_TAG, *args, file=sys.stderr)
+
+
+  def _note(self, *args):
+    """Emit a note to stderr."""
+    print(self._file_path + ': ' + self._NOTE_TAG, *args, file=sys.stderr)
+
+
+  def _load_elf_file(self, path, skip_bad_elf_magic):
+    """Load an ELF file from the `path`."""
+    try:
+      return ELFParser.open(path, self._llvm_readobj)
+    except (IOError, OSError):
+      self._error('Failed to open "{}".'.format(path))
+      sys.exit(2)
+    except ELFInvalidMagicError:
+      if skip_bad_elf_magic:
+        sys.exit(0)
+      else:
+        self._error('File "{}" must have a valid ELF magic word.'.format(path))
+        sys.exit(2)
+    except:
+      self._error('An unknown error occurred while opening "{}".'.format(path))
+      raise
+
+
+  def load_file_under_test(self, path, skip_bad_elf_magic,
+                           skip_unknown_elf_machine):
+    """Load file-under-test (either an executable or a shared lib)."""
+    self._file_path = path
+    self._file_under_test = self._load_elf_file(path, skip_bad_elf_magic)
+
+    if skip_unknown_elf_machine and \
+        self._file_under_test.header.e_machine not in _KNOWN_MACHINES:
+      sys.exit(0)
+
+
+  def load_shared_libs(self, shared_lib_paths):
+    """Load shared libraries."""
+    for path in shared_lib_paths:
+      self._shared_libs.append(self._load_elf_file(path, False))
+
+
+  def check_dt_soname(self, soname):
+    """Check whether DT_SONAME matches installation file name."""
+    if self._file_under_test.dt_soname != soname:
+      self._error('DT_SONAME "{}" must be equal to the file name "{}".'
+                  .format(self._file_under_test.dt_soname, soname))
+      sys.exit(2)
+
+
+  def check_dt_needed(self):
+    """Check whether all DT_NEEDED entries are specified in the build
+    system."""
+
+    missing_shared_libs = False
+
+    # Collect the DT_SONAMEs from shared libs specified in the build system.
+    specified_sonames = {lib.dt_soname for lib in self._shared_libs}
+
+    # Chech whether all DT_NEEDED entries are specified.
+    for lib in self._file_under_test.dt_needed:
+      if lib not in specified_sonames:
+        self._error('DT_NEEDED "{}" is not specified in shared_libs.'
+                    .format(lib.decode('utf-8')))
+        missing_shared_libs = True
+
+    if missing_shared_libs:
+      dt_needed = sorted(set(self._file_under_test.dt_needed))
+      modules = [re.sub('\\.so$', '', lib) for lib in dt_needed]
+
+      self._note()
+      self._note('Fix suggestions:')
+      self._note(
+        '  Android.bp: shared_libs: [' +
+        ', '.join('"' + module + '"' for module in modules) + '],')
+      self._note(
+        '  Android.mk: LOCAL_SHARED_LIBRARIES := ' + ' '.join(modules))
+
+      self._note()
+      self._note('If the fix above doesn\'t work, bypass this check with:')
+      self._note('  Android.bp: check_elf_files: false,')
+      self._note('  Android.mk: LOCAL_CHECK_ELF_FILES := false')
+
+      sys.exit(2)
+
+
+  @staticmethod
+  def _find_symbol(lib, name, version):
+    """Check whether the symbol name and version matches a definition in
+    lib."""
+    try:
+      lib_sym_vers = lib.exported[name]
+    except KeyError:
+      return False
+    if version == '':  # Symbol version is not requested
+      return True
+    return version in lib_sym_vers
+
+
+  @classmethod
+  def _find_symbol_from_libs(cls, libs, name, version):
+    """Check whether the symbol name and version is defined in one of the
+    shared libraries in libs."""
+    for lib in libs:
+      if cls._find_symbol(lib, name, version):
+        return lib
+    return None
+
+
+  def check_symbols(self):
+    """Check whether all undefined symbols are resolved to a definition."""
+    all_elf_files = [self._file_under_test] + self._shared_libs
+    missing_symbols = []
+    for sym, imported_vers in self._file_under_test.imported.iteritems():
+      for imported_ver in imported_vers:
+        lib = self._find_symbol_from_libs(all_elf_files, sym, imported_ver)
+        if not lib:
+          missing_symbols.append((sym, imported_ver))
+
+    if missing_symbols:
+      for sym, ver in sorted(missing_symbols):
+        sym = sym.decode('utf-8')
+        if ver:
+          sym += '@' + ver.decode('utf-8')
+        self._error('Unresolved symbol: {}'.format(sym))
+
+      self._note()
+      self._note('Some dependencies might be changed, thus the symbol(s) '
+                 'above cannot be resolved.')
+      self._note('Please re-build the prebuilt file: "{}".'
+                 .format(self._file_path))
+
+      self._note()
+      self._note('If this is a new prebuilt file and it is designed to have '
+                 'unresolved symbols, add one of the following properties:')
+      self._note('  Android.bp: allow_undefined_symbols: true,')
+      self._note('  Android.mk: LOCAL_ALLOW_UNDEFINED_SYMBOLS := true')
+
+      sys.exit(2)
+
+
+def _parse_args():
+  """Parse command line options."""
+  parser = argparse.ArgumentParser()
+
+  # Input file
+  parser.add_argument('file',
+                      help='Path to the input file to be checked')
+  parser.add_argument('--soname',
+                      help='Shared object name of the input file')
+
+  # Shared library dependencies
+  parser.add_argument('--shared-lib', action='append', default=[],
+                      help='Path to shared library dependencies')
+
+  # Check options
+  parser.add_argument('--skip-bad-elf-magic', action='store_true',
+                      help='Ignore the input file without the ELF magic word')
+  parser.add_argument('--skip-unknown-elf-machine', action='store_true',
+                      help='Ignore the input file with unknown machine ID')
+  parser.add_argument('--allow-undefined-symbols', action='store_true',
+                      help='Ignore unresolved undefined symbols')
+
+  # Other options
+  parser.add_argument('--llvm-readobj',
+                      help='Path to the llvm-readobj executable')
+
+  return parser.parse_args()
+
+
+def main():
+  """Main function"""
+  args = _parse_args()
+
+  llvm_readobj = args.llvm_readobj
+  if not llvm_readobj:
+    llvm_readobj = _get_llvm_readobj()
+
+  # Load ELF files
+  checker = Checker(llvm_readobj)
+  checker.load_file_under_test(
+    args.file, args.skip_bad_elf_magic, args.skip_unknown_elf_machine)
+  checker.load_shared_libs(args.shared_lib)
+
+  # Run checks
+  if args.soname:
+    checker.check_dt_soname(args.soname)
+
+  checker.check_dt_needed()
+
+  if not args.allow_undefined_symbols:
+    checker.check_symbols()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 8712d85..ba1d60e 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -569,11 +569,11 @@
   elif mount_point == "system_other":
     # We inherit the selinux policies of /system since we contain some of its
     # files.
-    copy_prop("avb_system_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_system_add_hashtree_footer_args",
+    copy_prop("avb_system_other_hashtree_enable", "avb_hashtree_enable")
+    copy_prop("avb_system_other_add_hashtree_footer_args",
               "avb_add_hashtree_footer_args")
-    copy_prop("avb_system_key_path", "avb_key_path")
-    copy_prop("avb_system_algorithm", "avb_algorithm")
+    copy_prop("avb_system_other_key_path", "avb_key_path")
+    copy_prop("avb_system_other_algorithm", "avb_algorithm")
     copy_prop("fs_type", "fs_type")
     copy_prop("system_fs_type", "fs_type")
     copy_prop("system_size", "partition_size")
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index be2c108..de3e3b8 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -588,6 +588,11 @@
     cmd.append("--second")
     cmd.append(fn)
 
+  fn = os.path.join(sourcedir, "dtb")
+  if os.access(fn, os.F_OK):
+    cmd.append("--dtb")
+    cmd.append(fn)
+
   fn = os.path.join(sourcedir, "cmdline")
   if os.access(fn, os.F_OK):
     cmd.append("--cmdline")
@@ -2221,12 +2226,6 @@
              collections.Counter(e.partition for e in block_diffs).items()
              if count > 1])
 
-    dynamic_partitions = set(shlex.split(info_dict.get(
-        "dynamic_partition_list", "").strip()))
-    assert set(block_diff_dict.keys()) == dynamic_partitions, \
-        "Dynamic partitions: {}, BlockDifference objects: {}".format(
-            list(dynamic_partitions), list(block_diff_dict.keys()))
-
     self._partition_updates = dict()
 
     for p, block_diff in block_diff_dict.items():
@@ -2258,6 +2257,22 @@
             "object is provided.".format(p, g)
         self._partition_updates[p].src_group = g
 
+    target_dynamic_partitions = set(shlex.split(info_dict.get(
+        "dynamic_partition_list", "").strip()))
+    block_diffs_with_target = set(p for p, u in self._partition_updates.items()
+                                  if u.tgt_size)
+    assert block_diffs_with_target == target_dynamic_partitions, \
+        "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
+            list(target_dynamic_partitions), list(block_diffs_with_target))
+
+    source_dynamic_partitions = set(shlex.split(source_info_dict.get(
+        "dynamic_partition_list", "").strip()))
+    block_diffs_with_source = set(p for p, u in self._partition_updates.items()
+                                  if u.src_size)
+    assert block_diffs_with_source == source_dynamic_partitions, \
+        "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
+            list(source_dynamic_partitions), list(block_diffs_with_source))
+
     if self._partition_updates:
       logger.info("Updating dynamic partitions %s",
                   self._partition_updates.keys())
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index c99049a..3619633 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -988,3 +988,201 @@
                              recovery_image, boot_image, self._info)
     validate_target_files.ValidateInstallRecoveryScript(self._tempdir,
                                                         self._info)
+
+
+class MockScriptWriter(object):
+  """A class that mocks edify_generator.EdifyGenerator.
+  """
+  def __init__(self, enable_comments=False):
+    self.lines = []
+    self.enable_comments = enable_comments
+  def Comment(self, comment):
+    if self.enable_comments:
+      self.lines.append("# {}".format(comment))
+  def AppendExtra(self, extra):
+    self.lines.append(extra)
+  def __str__(self):
+    return "\n".join(self.lines)
+
+
+class MockBlockDifference(object):
+  def __init__(self, partition, tgt, src=None):
+    self.partition = partition
+    self.tgt = tgt
+    self.src = src
+  def WriteScript(self, script, _, progress=None,
+                  write_verify_script=False):
+    if progress:
+      script.AppendExtra("progress({})".format(progress))
+    script.AppendExtra("patch({});".format(self.partition))
+    if write_verify_script:
+      self.WritePostInstallVerifyScript(script)
+  def WritePostInstallVerifyScript(self, script):
+    script.AppendExtra("verify({});".format(self.partition))
+
+
+class FakeSparseImage(object):
+  def __init__(self, size):
+    self.blocksize = 4096
+    self.total_blocks = size // 4096
+    assert size % 4096 == 0, "{} is not a multiple of 4096".format(size)
+
+
+class DynamicPartitionsDifferenceTest(test_utils.ReleaseToolsTestCase):
+  @staticmethod
+  def get_op_list(output_path):
+    with zipfile.ZipFile(output_path, 'r') as output_zip:
+      with output_zip.open("dynamic_partitions_op_list") as op_list:
+        return [line.strip() for line in op_list.readlines()
+                if not line.startswith("#")]
+
+  def setUp(self):
+    self.script = MockScriptWriter()
+    self.output_path = common.MakeTempFile(suffix='.zip')
+
+  def test_full(self):
+    target_info = common.LoadDictionaryFromLines("""
+dynamic_partition_list=system vendor
+super_partition_groups=group_foo
+super_group_foo_group_size={group_size}
+super_group_foo_partition_list=system vendor
+""".format(group_size=4 * GiB).split("\n"))
+    block_diffs = [MockBlockDifference("system", FakeSparseImage(3 * GiB)),
+                   MockBlockDifference("vendor", FakeSparseImage(1 * GiB))]
+
+    dp_diff = common.DynamicPartitionsDifference(target_info, block_diffs)
+    with zipfile.ZipFile(self.output_path, 'w') as output_zip:
+      dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
+
+    self.assertEqual(str(self.script).strip(), """
+assert(update_dynamic_partitions(package_extract_file("dynamic_partitions_op_list")));
+patch(vendor);
+verify(vendor);
+unmap_partition("vendor");
+patch(system);
+verify(system);
+unmap_partition("system");
+""".strip())
+
+    lines = self.get_op_list(self.output_path)
+
+    remove_all_groups = lines.index("remove_all_groups")
+    add_group = lines.index("add_group group_foo 4294967296")
+    add_vendor = lines.index("add vendor group_foo")
+    add_system = lines.index("add system group_foo")
+    resize_vendor = lines.index("resize vendor 1073741824")
+    resize_system = lines.index("resize system 3221225472")
+
+    self.assertLess(remove_all_groups, add_group,
+                    "Should add groups after removing all groups")
+    self.assertLess(add_group, min(add_vendor, add_system),
+                    "Should add partitions after adding group")
+    self.assertLess(add_system, resize_system,
+                    "Should resize system after adding it")
+    self.assertLess(add_vendor, resize_vendor,
+                    "Should resize vendor after adding it")
+
+  def test_inc_groups(self):
+    source_info = common.LoadDictionaryFromLines("""
+super_partition_groups=group_foo group_bar group_baz
+super_group_foo_group_size={group_foo_size}
+super_group_bar_group_size={group_bar_size}
+""".format(group_foo_size=4 * GiB, group_bar_size=3 * GiB).split("\n"))
+    target_info = common.LoadDictionaryFromLines("""
+super_partition_groups=group_foo group_baz group_qux
+super_group_foo_group_size={group_foo_size}
+super_group_baz_group_size={group_baz_size}
+super_group_qux_group_size={group_qux_size}
+""".format(group_foo_size=3 * GiB, group_baz_size=4 * GiB,
+           group_qux_size=1 * GiB).split("\n"))
+
+    dp_diff = common.DynamicPartitionsDifference(target_info,
+                                                 block_diffs=[],
+                                                 source_info_dict=source_info)
+    with zipfile.ZipFile(self.output_path, 'w') as output_zip:
+      dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
+
+    lines = self.get_op_list(self.output_path)
+
+    removed = lines.index("remove_group group_bar")
+    shrunk = lines.index("resize_group group_foo 3221225472")
+    grown = lines.index("resize_group group_baz 4294967296")
+    added = lines.index("add_group group_qux 1073741824")
+
+    self.assertLess(max(removed, shrunk) < min(grown, added),
+                    "ops that remove / shrink partitions must precede ops that "
+                    "grow / add partitions")
+
+  def test_inc_partitions(self):
+    source_info = common.LoadDictionaryFromLines("""
+dynamic_partition_list=system vendor product product_services
+super_partition_groups=group_foo
+super_group_foo_group_size={group_foo_size}
+super_group_foo_partition_list=system vendor product product_services
+""".format(group_foo_size=4 * GiB).split("\n"))
+    target_info = common.LoadDictionaryFromLines("""
+dynamic_partition_list=system vendor product odm
+super_partition_groups=group_foo group_bar
+super_group_foo_group_size={group_foo_size}
+super_group_foo_partition_list=system vendor odm
+super_group_bar_group_size={group_bar_size}
+super_group_bar_partition_list=product
+""".format(group_foo_size=3 * GiB, group_bar_size=1 * GiB).split("\n"))
+
+    block_diffs = [MockBlockDifference("system", FakeSparseImage(1536 * MiB),
+                                       src=FakeSparseImage(1024 * MiB)),
+                   MockBlockDifference("vendor", FakeSparseImage(512 * MiB),
+                                       src=FakeSparseImage(1024 * MiB)),
+                   MockBlockDifference("product", FakeSparseImage(1024 * MiB),
+                                       src=FakeSparseImage(1024 * MiB)),
+                   MockBlockDifference("product_services", None,
+                                       src=FakeSparseImage(1024 * MiB)),
+                   MockBlockDifference("odm", FakeSparseImage(1024 * MiB),
+                                       src=None)]
+
+    dp_diff = common.DynamicPartitionsDifference(target_info, block_diffs,
+                                                 source_info_dict=source_info)
+    with zipfile.ZipFile(self.output_path, 'w') as output_zip:
+      dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
+
+    metadata_idx = self.script.lines.index(
+        'assert(update_dynamic_partitions(package_extract_file('
+        '"dynamic_partitions_op_list")));')
+    self.assertLess(self.script.lines.index('patch(vendor);'), metadata_idx)
+    self.assertLess(metadata_idx, self.script.lines.index('verify(vendor);'))
+    for p in ("product", "system", "odm"):
+      patch_idx = self.script.lines.index("patch({});".format(p))
+      verify_idx = self.script.lines.index("verify({});".format(p))
+      self.assertLess(metadata_idx, patch_idx,
+                      "Should patch {} after updating metadata".format(p))
+      self.assertLess(patch_idx, verify_idx,
+                      "Should verify {} after patching".format(p))
+
+    self.assertNotIn("patch(product_services);", self.script.lines)
+
+    lines = self.get_op_list(self.output_path)
+
+    remove = lines.index("remove product_services")
+    move_product_out = lines.index("move product default")
+    shrink = lines.index("resize vendor 536870912")
+    shrink_group = lines.index("resize_group group_foo 3221225472")
+    add_group_bar = lines.index("add_group group_bar 1073741824")
+    add_odm = lines.index("add odm group_foo")
+    grow_existing = lines.index("resize system 1610612736")
+    grow_added = lines.index("resize odm 1073741824")
+    move_product_in = lines.index("move product group_bar")
+
+    max_idx_move_partition_out_foo = max(remove, move_product_out, shrink)
+    min_idx_move_partition_in_foo = min(add_odm, grow_existing, grow_added)
+
+    self.assertLess(max_idx_move_partition_out_foo, shrink_group,
+                    "Must shrink group after partitions inside group are shrunk"
+                    " / removed")
+
+    self.assertLess(add_group_bar, move_product_in,
+                    "Must add partitions to group after group is added")
+
+    self.assertLess(max_idx_move_partition_out_foo,
+                    min_idx_move_partition_in_foo,
+                    "Must shrink partitions / remove partitions from group"
+                    "before adding / moving partitions into group")