Merge "Set an empty persist.sys.dalvik.vm.lib.2 to simplify debug apex testing."
diff --git a/core/Makefile b/core/Makefile
index 030b3fe..c45fc15 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -901,9 +901,11 @@
 
 INTERNAL_INIT_BOOT_IMAGE_ARGS :=
 
+INTERNAL_BOOT_HAS_RAMDISK :=
 ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
   ifneq ($(BUILDING_INIT_BOOT_IMAGE),true)
     INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+    INTERNAL_BOOT_HAS_RAMDISK := true
   else
     INTERNAL_INIT_BOOT_IMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
   endif
@@ -952,21 +954,48 @@
     --os_version $(PLATFORM_VERSION_LAST_STABLE) \
     --os_patch_level $(PLATFORM_SECURITY_PATCH)
 
-ifdef BOARD_GKI_SIGNING_KEY_PATH
-ifndef BOARD_GKI_SIGNING_ALGORITHM
-$(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
-endif
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS := \
-    --gki_signing_key $(BOARD_GKI_SIGNING_KEY_PATH) \
-    --gki_signing_algorithm $(BOARD_GKI_SIGNING_ALGORITHM) \
-    --gki_signing_avbtool_path $(AVBTOOL)
-endif
+# $(1): image target to certify
+# $(2): out certificate target
+# $(3): image name
+# $(4): additional AVB arguments
+define generate_generic_boot_image_certificate
+  rm -rf "$(2)"
+  mkdir -p "$(dir $(2))"
+  $(GENERATE_GKI_CERTIFICATE) $(INTERNAL_GKI_CERTIFICATE_ARGS) \
+    --additional_avb_args "$(4)" \
+    --name "$(3)" --output "$(2)" "$(1)"
+endef
 
-# Using double quote to pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string
-# to MKBOOTIMG, although it may contain multiple args.
-ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS += \
-    --gki_signing_signature_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+INTERNAL_GKI_CERTIFICATE_ARGS :=
+INTERNAL_GKI_CERTIFICATE_DEPS :=
+INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE :=
+ifdef BOARD_GKI_SIGNING_KEY_PATH
+  ifndef BOARD_GKI_SIGNING_ALGORITHM
+    $(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
+  endif
+
+  INTERNAL_GKI_CERTIFICATE_ARGS := \
+    --key "$(BOARD_GKI_SIGNING_KEY_PATH)" \
+    --algorithm "$(BOARD_GKI_SIGNING_ALGORITHM)" \
+    --avbtool "$(AVBTOOL)"
+
+  # Quote and pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string argument.
+  ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
+    INTERNAL_GKI_CERTIFICATE_ARGS += --additional_avb_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+  endif
+
+  INTERNAL_GKI_CERTIFICATE_DEPS := \
+    $(GENERATE_GKI_CERTIFICATE) \
+    $(BOARD_GKI_SIGNING_KEY_PATH) \
+    $(AVBTOOL)
+
+  ifdef INSTALLED_RAMDISK_TARGET
+    INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE := \
+      $(call intermediates-dir-for,PACKAGING,generic_ramdisk)/boot_signature
+
+    $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE): $(INSTALLED_RAMDISK_TARGET) $(INTERNAL_GKI_CERTIFICATE_DEPS)
+	$(call generate_generic_boot_image_certificate,$(INSTALLED_RAMDISK_TARGET),$@,generic_ramdisk,$(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS))
+  endif
 endif
 
 # Define these only if we are building boot
@@ -983,8 +1012,15 @@
 
 # $1: boot image target
 define build_boot_board_avb_enabled
-  $(MKBOOTIMG) --kernel $(call bootimage-to-kernel,$(1)) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
-               $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(1)
+  $(eval kernel := $(call bootimage-to-kernel,$(1)))
+  $(if $(BOARD_GKI_SIGNING_KEY_PATH), \
+    $(eval kernel_signature := $(call intermediates-dir-for,PACKAGING,generic_kernel)/$(notdir $(kernel)).boot_signature) \
+    $(call generate_generic_boot_image_certificate,$(kernel),$(kernel_signature),generic_kernel,$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)) $(newline) \
+    $(if $(INTERNAL_BOOT_HAS_RAMDISK), \
+      cat $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE) >> $(kernel_signature) $(newline)))
+  $(MKBOOTIMG) --kernel $(kernel) $(INTERNAL_BOOTIMAGE_ARGS) \
+    $(if $(BOARD_GKI_SIGNING_KEY_PATH),--boot_signature "$(kernel_signature)",$(INTERNAL_MKBOOTIMG_VERSION_ARGS)) \
+    $(BOARD_MKBOOTIMG_ARGS) --output $(1)
   $(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(call get-bootimage-partition-size,$(1),boot)))
   $(AVBTOOL) add_hash_footer \
           --image $(1) \
@@ -993,12 +1029,15 @@
           $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
 endef
 
-$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+ifdef INTERNAL_BOOT_HAS_RAMDISK
+$(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)
+endif
+$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_board_avb_enabled,$@)
 
 .PHONY: bootimage-nodeps
-bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
 	@echo "make $@: ignoring dependencies"
 	$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET),$(call build_boot_board_avb_enabled,$(b)))
 
@@ -1097,9 +1136,12 @@
 endif
 
 ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)
 $(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
 	$(call pretty,"Target init_boot image: $@")
-	$(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_INIT_ARGS) --output $@
+	$(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) \
+	  $(if $(BOARD_GKI_SIGNING_KEY_PATH),--boot_signature "$(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)",$(INTERNAL_MKBOOTIMG_VERSION_ARGS)) \
+	  $(BOARD_MKBOOTIMG_INIT_ARGS) --output "$@"
 	$(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
 	$(AVBTOOL) add_hash_footer \
            --image $@ \
@@ -2117,6 +2159,9 @@
 
 ifdef TARGET_RECOVERY_FSTAB
 recovery_fstab := $(TARGET_RECOVERY_FSTAB)
+else ifdef TARGET_RECOVERY_FSTAB_GENRULE
+# Specifies a soong genrule module that generates an fstab.
+recovery_fstab := $(call intermediates-dir-for,ETC,$(TARGET_RECOVERY_FSTAB_GENRULE))/$(TARGET_RECOVERY_FSTAB_GENRULE)
 else
 recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
 endif
@@ -2757,12 +2802,10 @@
 
 # Generate fsv_meta
 fsverity-metadata-targets := $(sort $(filter \
-  $(TARGET_OUT)/framework/%.jar \
-  $(foreach arch,$(TARGET_ARCH) $(TARGET_2ND_ARCH),$(foreach ext,oat vdex art, \
-    $(TARGET_OUT)/framework/oat/$(arch)/%.$(ext))) \
+  $(TARGET_OUT)/framework/% \
   $(TARGET_OUT)/etc/boot-image.prof \
   $(TARGET_OUT)/etc/dirty-image-objects \
-  $(TARGET_OUT)/etc/updatable-bcp-packages.txt, \
+  $(TARGET_OUT)/etc/classpaths/%.pb, \
   $(ALL_GENERATED_SOURCES) $(ALL_DEFAULT_INSTALLED_MODULES)))
 
 define fsverity-generate-metadata
@@ -3773,6 +3816,13 @@
     --prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
 endif
 
+# For upgrading devices without a init_boot partition, the init_boot footer args
+# should fallback to boot partition footer.
+ifndef INSTALLED_INIT_BOOT_IMAGE_TARGET
+BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
+    $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+endif
+
 BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
 INIT_BOOT_FOOTER_ARGS := BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS
 VENDOR_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS
@@ -4484,6 +4534,7 @@
   fec \
   fsck.f2fs \
   fs_config \
+  generate_gki_certificate \
   generate_verity_key \
   host_init_verifier \
   img2simg \
@@ -6292,6 +6343,10 @@
 haiku: $(SOONG_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_FUZZ_TARGETS)
 $(call dist-for-goals,haiku,$(SOONG_FUZZ_PACKAGING_ARCH_MODULES))
 
+.PHONY: haiku-java
+haiku-java: $(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_TARGETS)
+$(call dist-for-goals,haiku-java,$(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES))
+
 .PHONY: haiku-rust
 haiku-rust: $(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_RUST_FUZZ_TARGETS)
 $(call dist-for-goals,haiku-rust,$(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES))
diff --git a/core/config.mk b/core/config.mk
index 3c7c5ce..6a50738 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -593,6 +593,7 @@
 MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)
 MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)
 LZ4 := $(HOST_OUT_EXECUTABLES)/lz4$(HOST_EXECUTABLE_SUFFIX)
+GENERATE_GKI_CERTIFICATE := $(HOST_OUT_EXECUTABLES)/generate_gki_certificate$(HOST_EXECUTABLE_SUFFIX)
 ifeq (,$(strip $(BOARD_CUSTOM_MKBOOTIMG)))
 MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
 else
diff --git a/core/envsetup.mk b/core/envsetup.mk
index b673050..8232907 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -317,11 +317,17 @@
 # Dumps all variables that match [A-Z][A-Z0-9_]* (with a few exceptions)
 # to the file at $(1). It is used to print only the variables that are
 # likely to be relevant to the product or board configuration.
+# Soong config variables are dumped as $(call soong_config_set) calls
+# instead of the raw variable values, because mk2rbc can't read the
+# raw ones.
 define dump-variables-rbc
 $(file >$(OUT_DIR)/dump-variables-rbc-temp.txt,$(subst $(space),$(newline),$(.VARIABLES)))\
 $(file >$(1),\
 $(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-variables-rbc-temp.txt | grep -vhE "^(SOONG_.*|LOCAL_PATH|TOPDIR|PRODUCT_COPY_OUT_.*)$$"),\
-$(v) := $(strip $($(v)))$(newline)))
+$(v) := $(strip $($(v)))$(newline))\
+$(foreach ns,$(SOONG_CONFIG_NAMESPACES),\
+$(foreach v,$(SOONG_CONFIG_$(ns)),\
+$$(call soong_config_set,$(ns),$(v),$(SOONG_CONFIG_$(ns)_$(v)))$(newline))))
 endef
 
 # Read the product specs so we can get TARGET_DEVICE and other
diff --git a/core/main.mk b/core/main.mk
index 7a126da..56007e2 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1877,6 +1877,10 @@
     )
   endif
 
+  ifeq ($(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST),true)
+    $(call dist-for-goals, droidcore-unbundled, $(INSTALLED_BOOTIMAGE_TARGET))
+  endif
+
   ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
     $(call dist-for-goals, droidcore-unbundled, \
       $(recovery_ramdisk) \
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index b7a2d0d..3a59f6c 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -43,7 +43,6 @@
     CameraExtensionsProxy \
     CaptivePortalLogin \
     CertInstaller \
-    clatd \
     DocumentsUI \
     DownloadProviderUi \
     EasterEgg \
diff --git a/tools/compliance/cmd/listshare_test.go b/tools/compliance/cmd/listshare_test.go
index 2ee249d..71a0be6 100644
--- a/tools/compliance/cmd/listshare_test.go
+++ b/tools/compliance/cmd/listshare_test.go
@@ -357,6 +357,98 @@
 			roots:       []string{"lib/libd.so.meta_lic"},
 			expectedOut: []projectShare{},
 		},
+		{
+			condition: "regressgpl1",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl1",
+			name:      "containerplus",
+			roots:     []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl2",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/gpl",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl2",
+			name:      "containerplus",
+			roots:     []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/gpl",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
diff --git a/tools/compliance/cmd/testdata/README.md b/tools/compliance/cmd/testdata/README.md
index 9872c04..07564c2 100644
--- a/tools/compliance/cmd/testdata/README.md
+++ b/tools/compliance/cmd/testdata/README.md
@@ -1,9 +1,12 @@
 ## Test data
 
-Each directory under testdata/ defines a similar build graph.
+Each non-regression directory under testdata/ defines a similar build graph.
 All have the same structure, but different versions of the graph have different
 license metadata.
 
+The regression* directories can have whatever structure is required for the
+specific test case.
+
 ### Testdata build graph structure:
 
 The structure is meant to simulate some common scenarios:
@@ -70,6 +73,7 @@
 
 #### a pure aggregation `container.zip` that merely bundles files together
 
+```dot
 strict digraph {
 	rankdir=LR;
 	bin1 [label="bin/bin1.meta_lic"];
@@ -89,6 +93,7 @@
 	container -> libb [label="static"];
 	{rank=same; container}
 }
+```
 
 #### an apex file (more like an apk file) with some binaries and libraries
 
diff --git a/tools/compliance/cmd/testdata/regressgpl1/README.md b/tools/compliance/cmd/testdata/regressgpl1/README.md
new file mode 100644
index 0000000..6ab872d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/README.md
@@ -0,0 +1,32 @@
+## Shipped versus non-shipped libraries with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries only needs to be shared if shipped
+alongside the container with the binaries.
+
+```dot
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic\nnotice"];
+	bin2 [label="bin/bin2.meta_lic\nnotice"];
+	bin3 [label="bin/bin3.meta_lic\nnotice"];
+	container [label="container.zip.meta_lic\nnotice"];
+	libapache [label="lib/libapache.so.meta_lic\nnotice"];
+	libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+	libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+	container -> bin1[label="static"];
+	container -> bin2 [label="static"];
+	container -> bin3 [label="static"];
+	bin1 -> libcxx [label="dynamic"];
+	bin2 -> libapache [label="dynamic"];
+	bin2 -> libcxx [label="dynamic"];
+	bin3 -> libapache [label="dynamic"];
+	bin3 -> libcxx [label="dynamic"];
+	bin3 -> libgpl [label="dynamic"];
+	libapache -> libcxx [label="dynamic"];
+	libgpl -> libcxx [label="dynamic"];
+	{rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
new file mode 100644
index 0000000..4afd240
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/onelibrary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
new file mode 100644
index 0000000..4e56fa3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/twolibraries"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
new file mode 100644
index 0000000..16290d3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "bin/threelibraries"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libgpl.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libgpl.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
new file mode 100644
index 0000000..295bcdb
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
@@ -0,0 +1,32 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin3.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..9184501
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+projects:  "lib/apache"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "lib/c++"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed:  "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..a3afa2b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name:  "External"
+projects:  "lib/gpl"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed:  "out/target/product/fictional/system/lib/libgpl.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/README.md b/tools/compliance/cmd/testdata/regressgpl2/README.md
new file mode 100644
index 0000000..9da0f4d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/README.md
@@ -0,0 +1,35 @@
+## Libraries shipped inside container with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries needs to be shared when shipped as
+part of the container with the binaries.
+
+```dot
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic\nnotice"];
+	bin2 [label="bin/bin2.meta_lic\nnotice"];
+	bin3 [label="bin/bin3.meta_lic\nnotice"];
+	container [label="container.zip.meta_lic\nnotice"];
+	libapache [label="lib/libapache.so.meta_lic\nnotice"];
+	libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+	libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+	container -> bin1[label="static"];
+	container -> bin2 [label="static"];
+	container -> bin3 [label="static"];
+	container -> libapache [label="static"];
+	container -> libcxx [label="static"];
+	container -> libgpl [label="static"];
+	bin1 -> libcxx [label="dynamic"];
+	bin2 -> libapache [label="dynamic"];
+	bin2 -> libcxx [label="dynamic"];
+	bin3 -> libapache [label="dynamic"];
+	bin3 -> libcxx [label="dynamic"];
+	bin3 -> libgpl [label="dynamic"];
+	libapache -> libcxx [label="dynamic"];
+	libgpl -> libcxx [label="dynamic"];
+	{rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
new file mode 100644
index 0000000..839fd9c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/onelibrary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
new file mode 100644
index 0000000..96baaae
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/twolibraries"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
new file mode 100644
index 0000000..3cd8602
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "bin/threelibraries"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libgpl.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
new file mode 100644
index 0000000..71b68cd
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin3.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..ae47340
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+projects:  "lib/apache"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "lib/c++"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed:  "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..4e78697
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name:  "External"
+projects:  "lib/gpl"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed:  "out/target/product/fictional/system/lib/libgpl.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index f3123b2..7b2c290 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -162,6 +162,7 @@
     required: [
         "brillo_update_payload",
         "checkvintf",
+        "generate_gki_certificate",
         "minigzip",
         "lz4",
         "toybox",
@@ -236,6 +237,7 @@
         "boot_signer",
         "brotli",
         "bsdiff",
+        "generate_gki_certificate",
         "imgdiff",
         "minigzip",
         "lz4",
@@ -301,6 +303,7 @@
         "brotli",
         "bsdiff",
         "deapexer",
+        "generate_gki_certificate",
         "imgdiff",
         "minigzip",
         "lz4",
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 30dcf5b..e5c68bc 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1396,34 +1396,52 @@
   return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
 
 
-def AppendGkiSigningArgs(cmd):
-  """Append GKI signing arguments for mkbootimg."""
-  # e.g., --gki_signing_key path/to/signing_key
-  #       --gki_signing_algorithm SHA256_RSA4096"
+def _HasGkiCertificationArgs():
+  return ("gki_signing_key_path" in OPTIONS.info_dict and
+          "gki_signing_algorithm" in OPTIONS.info_dict)
 
+
+def _GenerateGkiCertificate(image, image_name, partition_name):
   key_path = OPTIONS.info_dict.get("gki_signing_key_path")
-  # It's fine that a non-GKI boot.img has no gki_signing_key_path.
-  if not key_path:
-    return
+  algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
 
   if not os.path.exists(key_path) and OPTIONS.search_path:
     new_key_path = os.path.join(OPTIONS.search_path, key_path)
     if os.path.exists(new_key_path):
       key_path = new_key_path
 
-  # Checks key_path exists, before appending --gki_signing_* args.
+  # Checks key_path exists, before processing --gki_signing_* args.
   if not os.path.exists(key_path):
     raise ExternalError(
         'gki_signing_key_path: "{}" not found'.format(key_path))
 
-  algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
-  if key_path and algorithm:
-    cmd.extend(["--gki_signing_key", key_path,
-                "--gki_signing_algorithm", algorithm])
+  output_certificate = tempfile.NamedTemporaryFile()
+  cmd = [
+      "generate_gki_certificate",
+      "--name", image_name,
+      "--algorithm", algorithm,
+      "--key", key_path,
+      "--output", output_certificate.name,
+      image,
+  ]
 
-    signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
-    if signature_args:
-      cmd.extend(["--gki_signing_signature_args", signature_args])
+  signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
+  signature_args = signature_args.strip()
+  if signature_args:
+    cmd.extend(["--additional_avb_args", signature_args])
+
+  args = OPTIONS.info_dict.get(
+      "avb_" + partition_name + "_add_hash_footer_args", "")
+  args = args.strip()
+  if args:
+    cmd.extend(["--additional_avb_args", args])
+
+  RunAndCheckOutput(cmd)
+
+  output_certificate.seek(os.SEEK_SET, 0)
+  data = output_certificate.read()
+  output_certificate.close()
+  return data
 
 
 def BuildVBMeta(image_path, partitions, name, needed_partitions):
@@ -1549,6 +1567,8 @@
   if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
     return None
 
+  kernel_path = os.path.join(sourcedir, kernel) if kernel else None
+
   if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
     return None
 
@@ -1563,8 +1583,8 @@
   mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
 
   cmd = [mkbootimg]
-  if kernel:
-    cmd += ["--kernel", os.path.join(sourcedir, kernel)]
+  if kernel_path is not None:
+    cmd.extend(["--kernel", kernel_path])
 
   fn = os.path.join(sourcedir, "second")
   if os.access(fn, os.F_OK):
@@ -1604,15 +1624,31 @@
   if args and args.strip():
     cmd.extend(shlex.split(args))
 
-  args = info_dict.get("mkbootimg_version_args")
-  if args and args.strip():
-    cmd.extend(shlex.split(args))
+  boot_signature = None
+  if _HasGkiCertificationArgs():
+    # Certify GKI images.
+    boot_signature_bytes = b''
+    if kernel_path is not None:
+      boot_signature_bytes += _GenerateGkiCertificate(
+          kernel_path, "generic_kernel", "boot")
+    if has_ramdisk:
+      boot_signature_bytes += _GenerateGkiCertificate(
+          ramdisk_img.name, "generic_ramdisk", "init_boot")
+
+    if len(boot_signature_bytes) > 0:
+      boot_signature = tempfile.NamedTemporaryFile()
+      boot_signature.write(boot_signature_bytes)
+      boot_signature.flush()
+      cmd.extend(["--boot_signature", boot_signature.name])
+  else:
+    # Certified GKI boot/init_boot image mustn't set 'mkbootimg_version_args'.
+    args = info_dict.get("mkbootimg_version_args")
+    if args and args.strip():
+      cmd.extend(shlex.split(args))
 
   if has_ramdisk:
     cmd.extend(["--ramdisk", ramdisk_img.name])
 
-  AppendGkiSigningArgs(cmd)
-
   img_unsigned = None
   if info_dict.get("vboot"):
     img_unsigned = tempfile.NamedTemporaryFile()
@@ -1690,6 +1726,9 @@
     ramdisk_img.close()
   img.close()
 
+  if boot_signature is not None:
+    boot_signature.close()
+
   return data
 
 
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 58f0e85..fb32cce 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -233,6 +233,10 @@
 
   --enable_zucchini
       Whether to enable to zucchini feature. Will generate smaller OTA but uses more memory.
+
+  --enable_lz4diff
+      Whether to enable lz4diff feature. Will generate smaller OTA for EROFS but
+      uses more memory.
 """
 
 from __future__ import print_function
@@ -302,7 +306,8 @@
 OPTIONS.enable_vabc_xor = True
 OPTIONS.force_minor_version = None
 OPTIONS.compressor_types = None
-OPTIONS.enable_zucchini = None
+OPTIONS.enable_zucchini = True
+OPTIONS.enable_lz4diff = False
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
@@ -1145,13 +1150,25 @@
     partition_timestamps_flags = GeneratePartitionTimestampFlags(
         metadata.postcondition.partition_state)
 
-  # Auto-check for compatibility only if --enable_zucchini omitted. Otherwise
-  # let user override zucchini settings. This is useful for testing.
-  if OPTIONS.enable_zucchini is None:
-    if not ota_utils.IsZucchiniCompatible(source_file, target_file):
-      additional_args += ["--enable_zucchini", "false"]
-  else:
-    additional_args += ["--enable_zucchini", str(OPTIONS.enable_zucchini).lower()]
+  if not ota_utils.IsZucchiniCompatible(source_file, target_file):
+    OPTIONS.enable_zucchini = False
+
+  additional_args += ["--enable_zucchini",
+                      str(OPTIONS.enable_zucchini).lower()]
+
+  if not ota_utils.IsLz4diffCompatible(source_file, target_file):
+    OPTIONS.enable_lz4diff = False
+
+  additional_args += ["--enable_lz4diff",
+                      str(OPTIONS.enable_lz4diff).lower()]
+
+  if source_file and OPTIONS.enable_lz4diff:
+    input_tmp = common.UnzipTemp(source_file, ["META/liblz4.so"])
+    liblz4_path = os.path.join(input_tmp, "META", "liblz4.so")
+    assert os.path.exists(
+        liblz4_path), "liblz4.so not found in META/ dir of target file {}".format(liblz4_path)
+    logger.info("Enabling lz4diff %s", liblz4_path)
+    additional_args += ["--liblz4_path", liblz4_path]
 
   if OPTIONS.disable_vabc:
     additional_args += ["--disable_vabc", "true"]
@@ -1333,13 +1350,18 @@
     elif o == "--vabc_downgrade":
       OPTIONS.vabc_downgrade = True
     elif o == "--enable_vabc_xor":
+      assert a.lower() in ["true", "false"]
       OPTIONS.enable_vabc_xor = a.lower() != "false"
     elif o == "--force_minor_version":
       OPTIONS.force_minor_version = a
     elif o == "--compressor_types":
       OPTIONS.compressor_types = a
     elif o == "--enable_zucchini":
+      assert a.lower() in ["true", "false"]
       OPTIONS.enable_zucchini = a.lower() != "false"
+    elif o == "--enable_lz4diff":
+      assert a.lower() in ["true", "false"]
+      OPTIONS.enable_lz4diff = a.lower() != "false"
     else:
       return False
     return True
@@ -1388,6 +1410,7 @@
                                  "force_minor_version=",
                                  "compressor_types=",
                                  "enable_zucchin=",
+                                 "enable_lz4diff=",
                              ], extra_option_handler=option_handler)
 
   if len(args) != 2:
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index a4ec9e2..6896f83 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -640,6 +640,28 @@
   return target_apex_proto.SerializeToString()
 
 
+def IsLz4diffCompatible(source_file: str, target_file: str):
+  """Check whether lz4diff versions in two builds are compatible
+
+  Args:
+    source_file: Path to source build's target_file.zip
+    target_file: Path to target build's target_file.zip
+
+  Returns:
+    bool true if and only if lz4diff versions are compatible
+  """
+  if source_file is None or target_file is None:
+    return False
+  # Right now we enable lz4diff as long as source build has liblz4.so.
+  # In the future we might introduce version system to lz4diff as well.
+  if zipfile.is_zipfile(source_file):
+    with zipfile.ZipFile(source_file, "r") as zfp:
+      return "META/liblz4.so" in zfp.namelist()
+  else:
+    assert os.path.isdir(source_file)
+    return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
+
+
 def IsZucchiniCompatible(source_file: str, target_file: str):
   """Check whether zucchini versions in two builds are compatible
 
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index e42d417..7dd365f 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1631,66 +1631,7 @@
     self.assertEqual('3', chained_partition_args[1])
     self.assertTrue(os.path.exists(chained_partition_args[2]))
 
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_NoSigningKeyPath(self):
-    # A non-GKI boot.img has no gki_signing_key_path.
-    common.OPTIONS.info_dict = {
-        # 'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-
-    # Tests no --gki_signing_* args are appended if there is no
-    # gki_signing_key_path.
-    cmd = ['mkbootimg', '--header_version', '4']
-    expected_cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-    self.assertEqual(cmd, expected_cmd)
-
-  def test_AppendGkiSigningArgs_NoSigningAlgorithm(self):
-    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
-    with open(pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(pubkey))
-
-    # Tests no --gki_signing_* args are appended if there is no
-    # gki_signing_algorithm.
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        # 'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-
-    cmd = ['mkbootimg', '--header_version', '4']
-    expected_cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs(self):
-    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
-    with open(pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(pubkey))
-
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-    cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-
-    expected_cmd = [
-      'mkbootimg', '--header_version', '4',
-      '--gki_signing_key', pubkey,
-      '--gki_signing_algorithm', 'SHA256_RSA4096',
-      '--gki_signing_signature_args', '--prop foo:bar'
-    ]
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_KeyPathNotFound(self):
+  def test_GenerateGkiCertificate_KeyPathNotFound(self):
     pubkey = os.path.join(self.testdata_dir, 'no_testkey_gki.pem')
     self.assertFalse(os.path.exists(pubkey))
 
@@ -1699,41 +1640,11 @@
         'gki_signing_algorithm': 'SHA256_RSA4096',
         'gki_signing_signature_args': '--prop foo:bar',
     }
-    cmd = ['mkbootimg', '--header_version', '4']
-    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
+    test_file = tempfile.NamedTemporaryFile()
+    self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+                      test_file.name, 'generic_kernel', 'boot')
 
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_SearchKeyPath(self):
-    pubkey = 'testkey_gki.pem'
-    self.assertFalse(os.path.exists(pubkey))
-
-    # Tests it should replace the pubkey with an existed key under
-    # OPTIONS.search_path, i.e., os.path.join(OPTIONS.search_path, pubkey).
-    search_path_dir = common.MakeTempDir()
-    search_pubkey = os.path.join(search_path_dir, pubkey)
-    with open(search_pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(search_pubkey))
-
-    common.OPTIONS.search_path = search_path_dir
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-    cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-
-    expected_cmd = [
-      'mkbootimg', '--header_version', '4',
-      '--gki_signing_key', search_pubkey,
-      '--gki_signing_algorithm', 'SHA256_RSA4096',
-      '--gki_signing_signature_args', '--prop foo:bar'
-    ]
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_SearchKeyPathNotFound(self):
+  def test_GenerateGkiCertificate_SearchKeyPathNotFound(self):
     pubkey = 'no_testkey_gki.pem'
     self.assertFalse(os.path.exists(pubkey))
 
@@ -1749,9 +1660,9 @@
         'gki_signing_algorithm': 'SHA256_RSA4096',
         'gki_signing_signature_args': '--prop foo:bar',
     }
-    cmd = ['mkbootimg', '--header_version', '4']
-    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
-
+    test_file = tempfile.NamedTemporaryFile()
+    self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+                      test_file.name, 'generic_kernel', 'boot')
 
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.