Merge "Support generating install rules in Soong"
diff --git a/core/Makefile b/core/Makefile
index 2e72727..0b55c55 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4323,6 +4323,7 @@
   shflags \
   sign_apex \
   sign_target_files_apks \
+  sign_virt_apex \
   signapk \
   simg2img \
   sload_f2fs \
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index c55b2f3..ee15b75 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -32,7 +32,7 @@
 $(call add_soong_config_var,ANDROID,BOARD_BUILD_SYSTEM_ROOT_IMAGE)
 $(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
 
-ifeq (,$(filter com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
+ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
   # Prebuilt module SDKs require prebuilt modules to work, and currently
   # prebuilt modules are only provided for com.google.android.xxx. If we can't
   # find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
@@ -54,7 +54,7 @@
   # Always build from source for the module targets. This ought to be covered by
   # the TARGET_BUILD_APPS check above, but there are test builds that don't set it.
   SOONG_CONFIG_art_module_source_build := true
-else ifdef MODULE_BUILD_FROM_SOURCE
+else ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
   # Build from source if other Mainline modules are.
   SOONG_CONFIG_art_module_source_build := true
 else ifneq (,$(filter true,$(NATIVE_COVERAGE) $(CLANG_COVERAGE)))
@@ -73,7 +73,7 @@
 else ifneq (,$(filter dex2oatds dex2oats,$(PRODUCT_HOST_PACKAGES)))
   # Some products depend on host tools that aren't available as prebuilts.
   SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(filter com.google.android.art,$(PRODUCT_PACKAGES)))
+else ifeq (,$(findstring com.google.android.art,$(PRODUCT_PACKAGES)))
   # TODO(b/192006406): There is currently no good way to control which prebuilt
   # APEX (com.google.android.art or com.android.art) gets picked for deapexing
   # to provide dex jars for hiddenapi and dexpreopting. Instead the AOSP APEX is
@@ -91,6 +91,6 @@
 $(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static)
 endif
 
-ifdef MODULE_BUILD_FROM_SOURCE
+ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
 $(call add_soong_config_var_value,ANDROID,module_build_from_source,true)
 endif
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index 7655b42..ea50313 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -423,6 +423,15 @@
 
 $(LOCAL_INSTALLED_MODULE): $(my_dexpreopt_config_for_postprocessing)
 
+# System server jars defined in Android.mk are deprecated.
+ifneq (true, $(PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS))
+  ifneq (,$(filter %:$(LOCAL_MODULE), $(PRODUCT_SYSTEM_SERVER_JARS) $(PRODUCT_APEX_SYSTEM_SERVER_JARS)))
+    $(error System server jars defined in Android.mk are deprecated. \
+      Convert $(LOCAL_MODULE) to Android.bp or temporarily disable the error \
+      with 'PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS := true')
+  endif
+endif
+
 ifdef LOCAL_DEX_PREOPT
   # System server jars must be copied into predefined locations expected by
   # dexpreopt. Copy rule must be exposed to Ninja (as it uses these files as
diff --git a/core/product.mk b/core/product.mk
index 1b3c9f5..23fb939 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -237,6 +237,8 @@
 _product_list_vars += PRODUCT_APEX_SYSTEM_SERVER_JARS
 # If true, then suboptimal order of system server jars does not cause an error.
 _product_single_value_vars += PRODUCT_BROKEN_SUBOPTIMAL_ORDER_OF_SYSTEM_SERVER_JARS
+# If true, then system server jars defined in Android.mk are supported.
+_product_single_value_vars += PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS
 
 # Additional system server jars to be appended at the end of the common list.
 # This is necessary to avoid jars reordering due to makefile inheritance order.
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 5219751..9876d56 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -83,7 +83,10 @@
                     if _options.format == "make":
                         print("SOONG_CONFIG_" + nsname, ":=", " ".join(nsvars.keys()))
                     for var, val in sorted(nsvars.items()):
-                        __print_attr("SOONG_CONFIG_%s_%s" % (nsname, var), val)
+                        if val:
+                            __print_attr("SOONG_CONFIG_%s_%s" % (nsname, var), val)
+                        else:
+                            print("SOONG_CONFIG_%s_%s :=" % (nsname, var))
             elif attr not in globals_base or globals_base[attr] != val:
                 __print_attr(attr, val)
 
@@ -279,19 +282,35 @@
     """Returns configuration item for the inherited module."""
     return (pcm_name,)
 
-def _add_soong_config_namespace(g, nsname):
-    """Adds given namespace."""
+def _soong_config_namespace(g, nsname):
+    """Adds given namespace if it does not exist."""
 
+    if g[_soong_config_namespaces_key].get(nsname):
+        return
     # A value cannot be updated, so we need to create a new dictionary
     old = g[_soong_config_namespaces_key]
     g[_soong_config_namespaces_key] = dict([(k,v) for k,v in old.items()] + [(nsname, {})])
 
-def _add_soong_config_var_value(g, nsname, var, value):
-    """Defines a variable and adds it to the given namespace."""
-    ns = g[_soong_config_namespaces_key].get(nsname)
-    if ns == None:
-        fail("no such namespace: " + nsname)
-    ns[var] = value
+def _soong_config_set(g, nsname, var, value):
+    """Assigns the value to the variable in the namespace."""
+    _soong_config_namespace(g, nsname)
+    g[_soong_config_namespaces_key][nsname][var]=value
+
+def _soong_config_append(g, nsname, var, value):
+    """Appends to the value of the variable in the namespace."""
+    _soong_config_namespace(g, nsname)
+    ns = g[_soong_config_namespaces_key][nsname]
+    oldv = ns.get(var)
+    if oldv == None:
+        ns[var] = value
+    else:
+        ns[var] += " " + value
+
+
+def _abspath(path):
+    """Provided for compatibility, to be removed later."""
+    return path
+
 
 def _addprefix(prefix, string_or_list):
     """Adds prefix and returns a list.
@@ -377,6 +396,18 @@
     """Returns basename."""
     return path.rsplit("/",1)[-1]
 
+def _board_platform_in(g, string_or_list):
+    """Returns true if board is in the list."""
+    board = g.get("TARGET_BOARD_PLATFORM","")
+    if not board:
+        return False
+    return board in __words(string_or_list)
+
+
+def _board_platform_is(g, s):
+    """True if board is the same as argument."""
+    return g.get("TARGET_BOARD_PLATFORM","") == s
+
 
 def _copy_files(l, outdir):
     """Generate <item>:<outdir>/item for each item."""
@@ -596,10 +627,14 @@
 # Settings used during debugging.
 _options = __get_options()
 rblf = struct(
-    add_soong_config_namespace = _add_soong_config_namespace,
-    add_soong_config_var_value = _add_soong_config_var_value,
+    soong_config_namespace = _soong_config_namespace,
+    soong_config_append = _soong_config_append,
+    soong_config_set = _soong_config_set,
+    abspath = _abspath,
     addprefix = _addprefix,
     addsuffix = _addsuffix,
+    board_platform_in = _board_platform_in,
+    board_platform_is = _board_platform_is,
     copy_files = _copy_files,
     copy_if_exists = _copy_if_exists,
     cfg = __h_cfg,
diff --git a/core/tasks/dex_preopt_check.mk b/core/tasks/dex_preopt_check.mk
new file mode 100644
index 0000000..bfa1ec5
--- /dev/null
+++ b/core/tasks/dex_preopt_check.mk
@@ -0,0 +1,18 @@
+# Checks that some critical dexpreopt output files are installed.
+
+# Inputs:
+# DISABLE_DEXPREOPT_CHECK: True if the check should be disabled.
+# PRODUCT_PACKAGES: The list of packages to be installed for the product.
+# ALL_DEFAULT_INSTALLED_MODULES: The full list of modules going to be installed.
+# DEXPREOPT_SYSTEMSERVER_ARTIFACTS: The list of compilation artifacts of system server jars, which
+# 	is generated by Soong in dexpreopt_check.go.
+
+ifneq (true,$(DISABLE_DEXPREOPT_CHECK))
+  # Skip the check if the system server is not installed for the product.
+  ifneq (,$(filter services,$(PRODUCT_PACKAGES)))
+    $(call maybe-print-list-and-error,\
+      $(filter-out $(ALL_DEFAULT_INSTALLED_MODULES),$(DEXPREOPT_SYSTEMSERVER_ARTIFACTS)),\
+      Missing compilation artifacts. Dexpreopting is not working for some system server jars \
+    )
+  endif
+endif
diff --git a/envsetup.sh b/envsetup.sh
index b5dc847..4301d73 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1459,7 +1459,7 @@
         > $ANDROID_PRODUCT_OUT/module-info.json.build.log 2>&1
 }
 
-# Verifies that module-info.txt exists, creating it if it doesn't.
+# Verifies that module-info.txt exists, returning nonzero if it doesn't.
 function verifymodinfo() {
     if [ ! "$ANDROID_PRODUCT_OUT" ]; then
         if [ "$QUIET_VERIFYMODINFO" != "true" ] ; then
@@ -1470,7 +1470,7 @@
 
     if [ ! -f "$ANDROID_PRODUCT_OUT/module-info.json" ]; then
         if [ "$QUIET_VERIFYMODINFO" != "true" ] ; then
-            echo "Could not find module-info.json. It will only be built once, and it can be updated with 'refreshmod'" >&2
+            echo "Could not find module-info.json. Please run 'refreshmod' first." >&2
         fi
         return 1
     fi
@@ -1589,6 +1589,10 @@
 function installmod() {
     if [[ $# -eq 0 ]]; then
         echo "usage: installmod [adb install arguments] <module>" >&2
+        echo "" >&2
+        echo "Only flags to be passed after the \"install\" in adb install are supported," >&2
+        echo "with the exception of -s. If -s is passed it will be placed before the \"install\"." >&2
+        echo "-s must be the first flag passed if it exists." >&2
         return 1
     fi
 
@@ -1603,9 +1607,18 @@
         echo "Module '$1' does not produce a file ending with .apk (try 'refreshmod' if there have been build changes?)" >&2
         return 1
     fi
+    local serial_device=""
+    if [[ "$1" == "-s" ]]; then
+        if [[ $# -le 2 ]]; then
+            echo "-s requires an argument" >&2
+            return 1
+        fi
+        serial_device="-s $2"
+        shift 2
+    fi
     local length=$(( $# - 1 ))
-    echo adb install ${@:1:$length} $_path
-    adb install ${@:1:$length} $_path
+    echo adb $serial_device install ${@:1:$length} $_path
+    adb $serial_device install ${@:1:$length} $_path
 }
 
 function _complete_android_module_names() {
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 13b3417..68dd980 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -327,7 +327,6 @@
     incident_report \
     ld.mc \
     lpdump \
-    mdnsd \
     minigzip \
     mke2fs \
     resize2fs \
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 285c8c7..399652c 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -75,6 +75,7 @@
 VNDK-core: android.hardware.graphics.allocator@4.0.so
 VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
 VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-core: android.hardware.health-V1-ndk.so
 VNDK-core: android.hardware.health.storage-V1-ndk.so
 VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
 VNDK-core: android.hardware.identity-V3-ndk.so
diff --git a/tests/device.rbc b/tests/device.rbc
index feefcf7..37c5d0c 100644
--- a/tests/device.rbc
+++ b/tests/device.rbc
@@ -23,12 +23,11 @@
 ### PRODUCT_PACKAGES += dev_after
 ### PRODUCT_COPY_FILES += $(call find-copy-subdir-files,audio_platform_info*.xml,device/google/redfin/audio,$(TARGET_COPY_OUT_VENDOR)/etc) xyz:/etc/xyz
 ### PRODUCT_COPY_FILES += $(call copy-files,x.xml y.xml,/etc)
-### $(call add_soong_namespace,NS1)
-### $(call add_soong_config_var_value,NS1,v1,abc)
-### $(call add_soong_config_var_value,NS1,v2,def)
-### $(call add_soong_namespace,NS2)
+### $(call add_soong_config_namespace,NS1)
+### $(call soong_config_append,NS1,v1,abc)
+### $(call soong_config_append,NS1,v2,def)
 ### $(call add_soong_config_var_value,NS2,v3,abc)
-### $(call add_soong_config_var_value,NS2,v3,xyz)
+### $(call soong_config_set,NS2,v3,xyz)
 
 load("//build/make/core:product_config.rbc", "rblf")
 load(":part1.rbc", _part1_init = "init")
@@ -50,10 +49,20 @@
   cfg["PRODUCT_COPY_FILES"] += rblf.copy_files("x.xml y.xml", "/etc")
   cfg["PRODUCT_COPY_FILES"] += rblf.copy_files(["from/sub/x", "from/sub/y"], "to")
 
-  rblf.add_soong_config_namespace(g, "NS1")
-  rblf.add_soong_config_var_value(g, "NS1", "v1", "abc")
-  rblf.add_soong_config_var_value(g, "NS1", "v2", "def")
-  rblf.add_soong_config_namespace(g, "NS2")
-  rblf.add_soong_config_var_value(g, "NS2", "v3", "abc")
-  rblf.add_soong_config_var_value(g, "NS2", "v3", "xyz")
+  rblf.soong_config_namespace(g, "NS1")
+  rblf.soong_config_append(g, "NS1", "v1", "abc")
+  rblf.soong_config_append(g, "NS1", "v2", "def")
+  rblf.soong_config_set(g, "NS2", "v3", "abc")
+  rblf.soong_config_set(g, "NS2", "v3", "xyz")
 
+  if rblf.board_platform_in(g, "board1 board2"):
+    cfg["PRODUCT_PACKAGES"] += ["bad_package"]
+  g["TARGET_BOARD_PLATFORM"] = "board1"
+  if rblf.board_platform_in(g, "board1 board2"):
+    cfg["PRODUCT_PACKAGES"] += ["board1_in"]
+  if rblf.board_platform_in(g, ["board3","board2"]):
+    cfg["PRODUCT_PACKAGES"] += ["bad_board_in"]
+  if rblf.board_platform_is(g, "board1"):
+    cfg["PRODUCT_PACKAGES"] += ["board1_is"]
+  if rblf.board_platform_is(g, "board2"):
+    cfg["PRODUCT_PACKAGES"] += ["bad_board1_is"]
diff --git a/tests/part1.rbc b/tests/part1.rbc
index 3e50751..ae79d32 100644
--- a/tests/part1.rbc
+++ b/tests/part1.rbc
@@ -26,3 +26,5 @@
   cfg["PRODUCT_COPY_FILES"] += ["part_from:part_to"]
   rblf.setdefault(handle, "PRODUCT_PRODUCT_PROPERTIES")
   cfg["PRODUCT_PRODUCT_PROPERTIES"] += ["part_properties"]
+  rblf.soong_config_namespace(g, "NS1")
+  rblf.soong_config_append(g, "NS1", "v1", "abc_part1")
diff --git a/tests/run.rbc b/tests/run.rbc
index eef217b..3bb9b55 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -69,7 +69,9 @@
       "PRODUCT_PACKAGES": [
           "dev",
           "inc",
-          "dev_after"
+          "dev_after",
+          "board1_in",
+          "board1_is",
       ],
       "PRODUCT_PRODUCT_PROPERTIES": ["part_properties"]
     },
@@ -80,7 +82,7 @@
 assert_eq(
     {
         "NS1": {
-            "v1": "abc",
+            "v1": "abc abc_part1",
             "v2": "def"
         },
         "NS2": {
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index 51ec434..ee0feae 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -52,9 +52,9 @@
 
 
 class ApexApkSigner(object):
-  """Class to sign the apk files in a apex payload image and repack the apex"""
+  """Class to sign the apk files and other files in an apex payload image and repack the apex"""
 
-  def __init__(self, apex_path, key_passwords, codename_to_api_level_map):
+  def __init__(self, apex_path, key_passwords, codename_to_api_level_map, avbtool=None, sign_tool=None):
     self.apex_path = apex_path
     if not key_passwords:
       self.key_passwords = dict()
@@ -63,9 +63,11 @@
     self.codename_to_api_level_map = codename_to_api_level_map
     self.debugfs_path = os.path.join(
         OPTIONS.search_path, "bin", "debugfs_static")
+    self.avbtool = avbtool if avbtool else "avbtool"
+    self.sign_tool = sign_tool
 
   def ProcessApexFile(self, apk_keys, payload_key, signing_args=None):
-    """Scans and signs the apk files and repack the apex
+    """Scans and signs the payload files and repack the apex
 
     Args:
       apk_keys: A dict that holds the signing keys for apk files.
@@ -84,7 +86,7 @@
     apk_entries = [name for name in entries_names if name.endswith('.apk')]
 
     # No need to sign and repack, return the original apex path.
-    if not apk_entries:
+    if not apk_entries and self.sign_tool is None:
       logger.info('No apk file to sign in %s', self.apex_path)
       return self.apex_path
 
@@ -99,15 +101,15 @@
         logger.warning('Apk path does not contain the intended directory name:'
                        ' %s', entry)
 
-    payload_dir, has_signed_apk = self.ExtractApexPayloadAndSignApks(
-        apk_entries, apk_keys)
-    if not has_signed_apk:
-      logger.info('No apk file has been signed in %s', self.apex_path)
+    payload_dir, has_signed_content = self.ExtractApexPayloadAndSignContents(
+        apk_entries, apk_keys, payload_key)
+    if not has_signed_content:
+      logger.info('No contents has been signed in %s', self.apex_path)
       return self.apex_path
 
     return self.RepackApexPayload(payload_dir, payload_key, signing_args)
 
-  def ExtractApexPayloadAndSignApks(self, apk_entries, apk_keys):
+  def ExtractApexPayloadAndSignContents(self, apk_entries, apk_keys, payload_key):
     """Extracts the payload image and signs the containing apk files."""
     if not os.path.exists(self.debugfs_path):
       raise ApexSigningError(
@@ -119,7 +121,7 @@
                    self.debugfs_path, 'extract', self.apex_path, payload_dir]
     common.RunAndCheckOutput(extract_cmd)
 
-    has_signed_apk = False
+    has_signed_content = False
     for entry in apk_entries:
       apk_path = os.path.join(payload_dir, entry)
       assert os.path.exists(self.apex_path)
@@ -137,8 +139,15 @@
       common.SignFile(
           unsigned_apk, apk_path, key_name, self.key_passwords.get(key_name),
           codename_to_api_level_map=self.codename_to_api_level_map)
-      has_signed_apk = True
-    return payload_dir, has_signed_apk
+      has_signed_content = True
+
+    if self.sign_tool:
+      logger.info('Signing payload contents in apex %s with %s', self.apex_path, self.sign_tool)
+      cmd = [self.sign_tool, '--avbtool', self.avbtool, payload_key, payload_dir]
+      common.RunAndCheckOutput(cmd)
+      has_signed_content = True
+
+    return payload_dir, has_signed_content
 
   def RepackApexPayload(self, payload_dir, payload_key, signing_args=None):
     """Rebuilds the apex file with the updated payload directory."""
@@ -310,7 +319,7 @@
 
 def SignUncompressedApex(avbtool, apex_file, payload_key, container_key,
                          container_pw, apk_keys, codename_to_api_level_map,
-                         no_hashtree, signing_args=None):
+                         no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current uncompressed APEX with the given payload/container keys.
 
   Args:
@@ -322,14 +331,16 @@
     codename_to_api_level_map: A dict that maps from codename to API level.
     no_hashtree: Don't include hashtree in the signed APEX.
     signing_args: Additional args to be passed to the payload signer.
+    sign_tool: A tool to sign the contents of the APEX.
 
   Returns:
     The path to the signed APEX file.
   """
-  # 1. Extract the apex payload image and sign the containing apk files. Repack
+  # 1. Extract the apex payload image and sign the files (e.g. APKs). Repack
   # the apex file after signing.
   apk_signer = ApexApkSigner(apex_file, container_pw,
-                             codename_to_api_level_map)
+                             codename_to_api_level_map,
+                             avbtool, sign_tool)
   apex_file = apk_signer.ProcessApexFile(apk_keys, payload_key, signing_args)
 
   # 2a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given
@@ -384,7 +395,7 @@
 
 def SignCompressedApex(avbtool, apex_file, payload_key, container_key,
                        container_pw, apk_keys, codename_to_api_level_map,
-                       no_hashtree, signing_args=None):
+                       no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current compressed APEX with the given payload/container keys.
 
   Args:
@@ -421,7 +432,8 @@
       apk_keys,
       codename_to_api_level_map,
       no_hashtree,
-      signing_args)
+      signing_args,
+      sign_tool)
 
   # 3. Compress signed original apex.
   compressed_apex_file = common.MakeTempFile(prefix='apex-container-',
@@ -449,7 +461,7 @@
 
 def SignApex(avbtool, apex_data, payload_key, container_key, container_pw,
              apk_keys, codename_to_api_level_map,
-             no_hashtree, signing_args=None):
+             no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current APEX with the given payload/container keys.
 
   Args:
@@ -485,7 +497,8 @@
           codename_to_api_level_map=codename_to_api_level_map,
           no_hashtree=no_hashtree,
           apk_keys=apk_keys,
-          signing_args=signing_args)
+          signing_args=signing_args,
+          sign_tool=sign_tool)
     elif apex_type == 'COMPRESSED':
       return SignCompressedApex(
           avbtool,
@@ -496,7 +509,8 @@
           codename_to_api_level_map=codename_to_api_level_map,
           no_hashtree=no_hashtree,
           apk_keys=apk_keys,
-          signing_args=signing_args)
+          signing_args=signing_args,
+          sign_tool=sign_tool)
     else:
       # TODO(b/172912232): support signing compressed apex
       raise ApexInfoError('Unsupported apex type {}'.format(apex_type))
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 5affa32..2ee4b8e 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -2107,7 +2107,7 @@
   devnull = open("/dev/null", "w+b")
   for k in sorted(keylist):
     # We don't need a password for things that aren't really keys.
-    if k in SPECIAL_CERT_STRINGS:
+    if k in SPECIAL_CERT_STRINGS or k is None:
       no_passwords.append(k)
       continue
 
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 4eb2f0c..17f373e 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1401,8 +1401,8 @@
     # We should only allow downgrading incrementals (as opposed to full).
     # Otherwise the device may go back from arbitrary build with this full
     # OTA package.
-    if OPTIONS.incremental_source is None:
-      raise ValueError("Cannot generate downgradable full OTAs")
+  if OPTIONS.incremental_source is None and OPTIONS.downgrade:
+    raise ValueError("Cannot generate downgradable full OTAs")
 
   # TODO(xunchang) for retrofit and partial updates, maybe we should rebuild the
   # target-file and reload the info_dict. So the info will be consistent with
diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py
index fb947f4..679f57a 100755
--- a/tools/releasetools/sign_apex.py
+++ b/tools/releasetools/sign_apex.py
@@ -39,6 +39,9 @@
   --codename_to_api_level_map Q:29,R:30,...
       A Mapping of codename to api level.  This is useful to provide sdk targeting
       information to APK Signer.
+
+  --sign_tool <sign_tool>
+      Optional flag that specifies a custom signing tool for the contents of the apex.
 """
 
 import logging
@@ -52,7 +55,7 @@
 
 
 def SignApexFile(avbtool, apex_file, payload_key, container_key, no_hashtree,
-                 apk_keys=None, signing_args=None, codename_to_api_level_map=None):
+                 apk_keys=None, signing_args=None, codename_to_api_level_map=None, sign_tool=None):
   """Signs the given apex file."""
   with open(apex_file, 'rb') as input_fp:
     apex_data = input_fp.read()
@@ -66,7 +69,8 @@
       codename_to_api_level_map=codename_to_api_level_map,
       no_hashtree=no_hashtree,
       apk_keys=apk_keys,
-      signing_args=signing_args)
+      signing_args=signing_args,
+      sign_tool=sign_tool)
 
 
 def main(argv):
@@ -100,6 +104,8 @@
         if 'extra_apks' not in options:
           options['extra_apks'] = {}
         options['extra_apks'].update({n: key})
+    elif o == '--sign_tool':
+      options['sign_tool'] = a
     else:
       return False
     return True
@@ -114,6 +120,7 @@
           'payload_extra_args=',
           'payload_key=',
           'extra_apks=',
+          'sign_tool=',
       ],
       extra_option_handler=option_handler)
 
@@ -133,7 +140,8 @@
       apk_keys=options.get('extra_apks', {}),
       signing_args=options.get('payload_extra_args'),
       codename_to_api_level_map=options.get(
-          'codename_to_api_level_map', {}))
+          'codename_to_api_level_map', {}),
+      sign_tool=options['sign_tool'])
   shutil.copyfile(signed_apex, args[1])
   logger.info("done.")
 
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index bd5d7d3..5626980 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -262,7 +262,7 @@
 
   Args:
     keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
-        container_key).
+        container_key, sign_tool).
     key_map: A dict that overrides the keys, specified via command-line input.
 
   Returns:
@@ -280,11 +280,11 @@
     if apex not in keys_info:
       logger.warning('Failed to find %s in target_files; Ignored', apex)
       continue
-    keys_info[apex] = (key, keys_info[apex][1])
+    keys_info[apex] = (key, keys_info[apex][1], keys_info[apex][2])
 
   # Apply the key remapping to container keys.
-  for apex, (payload_key, container_key) in keys_info.items():
-    keys_info[apex] = (payload_key, key_map.get(container_key, container_key))
+  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
+    keys_info[apex] = (payload_key, key_map.get(container_key, container_key), sign_tool)
 
   # Apply all the --extra_apks options to override the container keys.
   for apex, key in OPTIONS.extra_apks.items():
@@ -293,13 +293,13 @@
       continue
     if not key:
       key = 'PRESIGNED'
-    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key))
+    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key), keys_info[apex][2])
 
   # A PRESIGNED container entails a PRESIGNED payload. Apply this to all the
   # APEX key pairs. However, a PRESIGNED container with non-PRESIGNED payload
   # (overridden via commandline) indicates a config error, which should not be
   # allowed.
-  for apex, (payload_key, container_key) in keys_info.items():
+  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
     if container_key != 'PRESIGNED':
       continue
     if apex in OPTIONS.extra_apex_payload_keys:
@@ -311,7 +311,7 @@
       print(
           "Setting {} payload as PRESIGNED due to PRESIGNED container".format(
               apex))
-    keys_info[apex] = ('PRESIGNED', 'PRESIGNED')
+    keys_info[apex] = ('PRESIGNED', 'PRESIGNED', None)
 
   return keys_info
 
@@ -372,7 +372,7 @@
     compressed_extension: The extension string of compressed APKs, such as
         '.gz', or None if there's no compressed APKs.
     apex_keys: A dict that contains the key mapping from APEX name to
-        (payload_key, container_key).
+        (payload_key, container_key, sign_tool).
 
   Raises:
     AssertionError: On finding unknown APKs and APEXes.
@@ -417,7 +417,7 @@
 
     name = GetApexFilename(info.filename)
 
-    (payload_key, container_key) = apex_keys[name]
+    (payload_key, container_key, _) = apex_keys[name]
     if ((payload_key in common.SPECIAL_CERT_STRINGS and
          container_key not in common.SPECIAL_CERT_STRINGS) or
         (payload_key not in common.SPECIAL_CERT_STRINGS and
@@ -569,7 +569,7 @@
     elif IsApexFile(filename):
       name = GetApexFilename(filename)
 
-      payload_key, container_key = apex_keys[name]
+      payload_key, container_key, sign_tool = apex_keys[name]
 
       # We've asserted not having a case with only one of them PRESIGNED.
       if (payload_key not in common.SPECIAL_CERT_STRINGS and
@@ -588,7 +588,8 @@
             apk_keys,
             codename_to_api_level_map,
             no_hashtree=None,  # Let apex_util determine if hash tree is needed
-            signing_args=OPTIONS.avb_extra_args.get('apex'))
+            signing_args=OPTIONS.avb_extra_args.get('apex'),
+            sign_tool=sign_tool)
         common.ZipWrite(output_tf_zip, signed_apex, filename)
 
       else:
@@ -1147,15 +1148,16 @@
 
   Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
   dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
-  tuple of (payload_key, container_key).
+  tuple of (payload_key, container_key, sign_tool).
 
   Args:
     tf_zip: The input target_files ZipFile (already open).
 
   Returns:
-    (payload_key, container_key): payload_key contains the path to the payload
-        signing key; container_key contains the path to the container signing
-        key.
+    (payload_key, container_key, sign_tool):
+      - payload_key contains the path to the payload signing key
+      - container_key contains the path to the container signing key
+      - sign_tool is an apex-specific signing tool for its payload contents
   """
   keys = {}
   for line in tf_zip.read('META/apexkeys.txt').decode().split('\n'):
@@ -1168,7 +1170,8 @@
         r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
         r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
         r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*?)"'
-        r'(\s+partition="(?P<PARTITION>.*?)")?$',
+        r'(\s+partition="(?P<PARTITION>.*?)")?'
+        r'(\s+sign_tool="(?P<SIGN_TOOL>.*?)")?$',
         line)
     if not matches:
       continue
@@ -1197,7 +1200,8 @@
     else:
       raise ValueError("Failed to parse container keys: \n{}".format(line))
 
-    keys[name] = (payload_private_key, container_key)
+    sign_tool = matches.group("SIGN_TOOL")
+    keys[name] = (payload_private_key, container_key, sign_tool)
 
   return keys
 
diff --git a/tools/releasetools/test_apex_utils.py b/tools/releasetools/test_apex_utils.py
index 71f6433..ed920f2 100644
--- a/tools/releasetools/test_apex_utils.py
+++ b/tools/releasetools/test_apex_utils.py
@@ -187,3 +187,19 @@
 
     self.payload_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key')
     signer.ProcessApexFile(apk_keys, self.payload_key)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_ApexApkSigner_invokesCustomSignTool(self):
+    apex_path = common.MakeTempFile(suffix='.apex')
+    shutil.copy(self.apex_with_apk, apex_path)
+    apk_keys = {'wifi-service-resources.apk': os.path.join(
+        self.testdata_dir, 'testkey')}
+    self.payload_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key')
+
+    # pass `false` as a sign_tool to see the invocation error
+    with self.assertRaises(common.ExternalError) as cm:
+        signer = apex_utils.ApexApkSigner(apex_path, None, None, sign_tool='false')
+        signer.ProcessApexFile(apk_keys, self.payload_key)
+
+    the_exception = cm.exception
+    self.assertIn('Failed to run command \'[\'false\'', the_exception.message)
diff --git a/tools/releasetools/test_build_image.py b/tools/releasetools/test_build_image.py
index b24805f..cfae7a5 100644
--- a/tools/releasetools/test_build_image.py
+++ b/tools/releasetools/test_build_image.py
@@ -196,7 +196,7 @@
     p.communicate()
     self.assertEqual(0, p.returncode)
 
-    fs_dict = GetFilesystemCharacteristics(output_file)
+    fs_dict = GetFilesystemCharacteristics('ext4', output_file)
     self.assertEqual(int(fs_dict['Block size']), 4096)
     self.assertGreaterEqual(int(fs_dict['Free blocks']), 0) # expect ~88
     self.assertGreater(int(fs_dict['Inode count']), 0)      # expect ~64
diff --git a/tools/releasetools/test_sign_apex.py b/tools/releasetools/test_sign_apex.py
index 646b04d..8470f20 100644
--- a/tools/releasetools/test_sign_apex.py
+++ b/tools/releasetools/test_sign_apex.py
@@ -69,5 +69,5 @@
         payload_key,
         container_key,
         False,
-        codename_to_api_level_map={'S': 31})
+        codename_to_api_level_map={'S': 31, 'Tiramisu' : 32})
     self.assertTrue(os.path.exists(signed_apex))
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index ad9e657..92dca9a 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -328,23 +328,23 @@
         'Apex3.apex' : 'key3',
     }
     apex_keys = {
-        'Apex1.apex' : ('payload-key1', 'container-key1'),
-        'Apex2.apex' : ('payload-key2', 'container-key2'),
+        'Apex1.apex' : ('payload-key1', 'container-key1', None),
+        'Apex2.apex' : ('payload-key2', 'container-key2', None),
     }
     with zipfile.ZipFile(input_file) as input_zip:
       CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)
 
       # Fine to have both keys as PRESIGNED.
-      apex_keys['Apex2.apex'] = ('PRESIGNED', 'PRESIGNED')
+      apex_keys['Apex2.apex'] = ('PRESIGNED', 'PRESIGNED', None)
       CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)
 
       # Having only one of them as PRESIGNED is not allowed.
-      apex_keys['Apex2.apex'] = ('payload-key2', 'PRESIGNED')
+      apex_keys['Apex2.apex'] = ('payload-key2', 'PRESIGNED', None)
       self.assertRaises(
           AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
           None, apex_keys)
 
-      apex_keys['Apex2.apex'] = ('PRESIGNED', 'container-key1')
+      apex_keys['Apex2.apex'] = ('PRESIGNED', 'container-key1', None)
       self.assertRaises(
           AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
           None, apex_keys)
@@ -475,10 +475,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_mismatchingContainerKeys(self):
@@ -514,10 +514,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_missingPayloadPublicKey(self):
@@ -537,10 +537,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
@@ -560,10 +560,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
@@ -583,10 +583,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReplaceGkiSigningKey(self):