Merge "Silence more duplicate path warnings" into pi-dev
diff --git a/core/Makefile b/core/Makefile
index c03f21f..7911026 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1261,19 +1261,23 @@
 recovery_wipe :=
 endif
 
-# Prior to A/B update, we used to have:
+# Traditionally with non-A/B OTA we have:
 #   boot.img + recovery-from-boot.p + recovery-resource.dat = recovery.img.
-# recovery-resource.dat is needed only if we carry a patch of the boot and
-# recovery images and invoke install-recovery.sh on the first boot post an
-# OTA update.
+# recovery-resource.dat is needed only if we carry an imgdiff patch of the boot and recovery images
+# and invoke install-recovery.sh on the first boot post an OTA update.
 #
 # We no longer need that if one of the following conditions holds:
-#   a) We carry a full copy of the recovery image
+#   a) We carry a full copy of the recovery image - no patching needed
 #      (BOARD_USES_FULL_RECOVERY_IMAGE = true);
-#   b) We build a single image that contains boot and recovery both
-#      (BOARD_USES_RECOVERY_AS_BOOT = true).
+#   b) We build a single image that contains boot and recovery both - no recovery image to install
+#      (BOARD_USES_RECOVERY_AS_BOOT = true);
+#   c) We build the root into system image - not needing the resource file as we do bsdiff
+#      (BOARD_BUILD_SYSTEM_ROOT_IMAGE = true).
+# Note that condition b) implies condition c), because of the earlier check in this file:
+# "BOARD_USES_RECOVERY_AS_BOOT = true must have BOARD_BUILD_SYSTEM_ROOT_IMAGE = true" (not vice
+# versa though).
 
-ifeq (,$(filter true, $(BOARD_USES_FULL_RECOVERY_IMAGE) $(BOARD_USES_RECOVERY_AS_BOOT)))
+ifeq (,$(filter true, $(BOARD_USES_FULL_RECOVERY_IMAGE) $(BOARD_BUILD_SYSTEM_ROOT_IMAGE)))
 # Named '.dat' so we don't attempt to use imgdiff for patching it.
 RECOVERY_RESOURCE_ZIP := $(TARGET_OUT)/etc/recovery-resource.dat
 else
@@ -1560,15 +1564,21 @@
 # image size check calculation.
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 ifneq ($(BOARD_USES_FULL_RECOVERY_IMAGE),true)
+ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+diff_tool := $(HOST_OUT_EXECUTABLES)/bsdiff
+else
+diff_tool := $(HOST_OUT_EXECUTABLES)/imgdiff
+endif
 intermediates := $(call intermediates-dir-for,PACKAGING,recovery_patch)
 RECOVERY_FROM_BOOT_PATCH := $(intermediates)/recovery_from_boot.p
-$(RECOVERY_FROM_BOOT_PATCH): $(INSTALLED_RECOVERYIMAGE_TARGET) \
-                             $(INSTALLED_BOOTIMAGE_TARGET) \
-			     $(HOST_OUT_EXECUTABLES)/imgdiff \
-	                     $(HOST_OUT_EXECUTABLES)/bsdiff
+$(RECOVERY_FROM_BOOT_PATCH): PRIVATE_DIFF_TOOL := $(diff_tool)
+$(RECOVERY_FROM_BOOT_PATCH): \
+		$(INSTALLED_RECOVERYIMAGE_TARGET) \
+		$(INSTALLED_BOOTIMAGE_TARGET) \
+		$(diff_tool)
 	@echo "Construct recovery from boot"
 	mkdir -p $(dir $@)
-	PATH=$(HOST_OUT_EXECUTABLES):$$PATH $(HOST_OUT_EXECUTABLES)/imgdiff $(INSTALLED_BOOTIMAGE_TARGET) $(INSTALLED_RECOVERYIMAGE_TARGET) $@
+	$(PRIVATE_DIFF_TOOL) $(INSTALLED_BOOTIMAGE_TARGET) $(INSTALLED_RECOVERYIMAGE_TARGET) $@
 else # $(BOARD_USES_FULL_RECOVERY_IMAGE) == true
 RECOVERY_FROM_BOOT_PATCH := $(INSTALLED_RECOVERYIMAGE_TARGET)
 endif
@@ -2781,9 +2791,15 @@
 	$(hide) $(call fs_config,$(zip_root)/PRODUCT,product/) > $(zip_root)/META/product_filesystem_config.txt
 endif
 ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+	@# When using BOARD_BUILD_SYSTEM_ROOT_IMAGE, ROOT always contains the files for the root under
+	@# normal boot. BOOT/RAMDISK exists only if additionally using BOARD_USES_RECOVERY_AS_BOOT.
 	$(hide) $(call fs_config,$(zip_root)/ROOT,) > $(zip_root)/META/root_filesystem_config.txt
-endif
+ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
 	$(hide) $(call fs_config,$(zip_root)/BOOT/RAMDISK,) > $(zip_root)/META/boot_filesystem_config.txt
+endif
+else # BOARD_BUILD_SYSTEM_ROOT_IMAGE != true
+	$(hide) $(call fs_config,$(zip_root)/BOOT/RAMDISK,) > $(zip_root)/META/boot_filesystem_config.txt
+endif
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) $(call fs_config,$(zip_root)/RECOVERY/RAMDISK,) > $(zip_root)/META/recovery_filesystem_config.txt
 endif
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 9415143..35d077c 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -217,19 +217,17 @@
 
       # Respect LOCAL_NOSANITIZE for integer-overflow flags.
       ifeq ($(filter signed-integer-overflow, $(strip $(LOCAL_NOSANITIZE))),)
-        my_cflags += -fsanitize=signed-integer-overflow
+        my_sanitize += signed-integer-overflow
       endif
       ifeq ($(filter unsigned-integer-overflow, $(strip $(LOCAL_NOSANITIZE))),)
-        my_cflags += -fsanitize=unsigned-integer-overflow
+        my_sanitize += unsigned-integer-overflow
       endif
-      my_cflags += -fsanitize-trap=all
-      my_cflags += -ftrap-function=abort
       my_cflags += $(INTEGER_OVERFLOW_EXTRA_CFLAGS)
 
       # Check for diagnostics mode (on by default).
       ifneq ($(filter integer_overflow,$(my_sanitize_diag)),)
-        my_cflags += -fno-sanitize-trap=signed-integer-overflow,unsigned-integer-overflow
-        my_shared_libraries := $($(LOCAL_2ND_ARCH_VAR_PREFIX)UBSAN_RUNTIME_LIBRARY) $(my_shared_libraries)
+        my_sanitize_diag += signed-integer-overflow
+        my_sanitize_diag += unsigned-integer-overflow
       endif
     endif
   endif
@@ -326,6 +324,27 @@
   endif
 endif
 
+# Use minimal diagnostics when integer overflow is enabled
+ifndef LOCAL_IS_HOST_MODULE
+  # Pre-emptively add UBSAN minimal runtime incase a static library dependency requires it
+  ifeq ($(filter STATIC_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
+    ifndef LOCAL_SDK_VERSION
+      my_static_libraries += $($(LOCAL_2ND_ARCH_VAR_PREFIX)UBSAN_MINIMAL_RUNTIME_LIBRARY)
+    endif
+  endif
+  ifneq ($(filter unsigned-integer-overflow signed-integer-overflow integer,$(my_sanitize)),)
+    ifeq ($(filter unsigned-integer-overflow signed-integer overflow integer,$(my_sanitize_diag)),)
+      ifeq ($(filter cfi,$(my_sanitize_diag)),)
+        ifeq ($(filter address,$(my_sanitize)),)
+          my_cflags += -fsanitize-minimal-runtime
+          my_cflags += -fno-sanitize-trap=integer
+          my_cflags += -fno-sanitize-recover=integer
+        endif
+      endif
+    endif
+  endif
+endif
+
 ifneq ($(strip $(LOCAL_SANITIZE_RECOVER)),)
   recover_arg := $(subst $(space),$(comma),$(LOCAL_SANITIZE_RECOVER)),
   my_cflags += -fsanitize-recover=$(recover_arg)
diff --git a/target/board/generic/sepolicy/file.te b/target/board/generic/sepolicy/file.te
new file mode 100644
index 0000000..3afd706
--- /dev/null
+++ b/target/board/generic/sepolicy/file.te
@@ -0,0 +1 @@
+type mediadrm_vendor_data_file, file_type, data_file_type;
diff --git a/target/board/generic/sepolicy/file_contexts b/target/board/generic/sepolicy/file_contexts
index 521c65e..c65aaec 100644
--- a/target/board/generic/sepolicy/file_contexts
+++ b/target/board/generic/sepolicy/file_contexts
@@ -33,3 +33,7 @@
 /vendor/lib(64)?/lib_renderControl_enc\.so       u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libGLESv1_enc\.so       u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libGLESv2_enc\.so       u:object_r:same_process_hal_file:s0
+
+# data
+/data/vendor/mediadrm(/.*)?            u:object_r:mediadrm_vendor_data_file:s0
+
diff --git a/target/board/generic/sepolicy/hal_drm_widevine.te b/target/board/generic/sepolicy/hal_drm_widevine.te
index 42d462a..d49000d 100644
--- a/target/board/generic/sepolicy/hal_drm_widevine.te
+++ b/target/board/generic/sepolicy/hal_drm_widevine.te
@@ -10,3 +10,5 @@
 
 vndbinder_use(hal_drm_widevine);
 hal_client_domain(hal_drm_widevine, hal_graphics_composer);
+allow hal_drm_widevine mediadrm_vendor_data_file:dir create_dir_perms;
+allow hal_drm_widevine mediadrm_vendor_data_file:file create_file_perms;
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 743c6a0..787de98 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1828,35 +1828,47 @@
 
 def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
                       info_dict=None):
-  """Generate a binary patch that creates the recovery image starting
-  with the boot image.  (Most of the space in these images is just the
-  kernel, which is identical for the two, so the resulting patch
-  should be efficient.)  Add it to the output zip, along with a shell
-  script that is run from init.rc on first boot to actually do the
-  patching and install the new recovery image.
+  """Generates the recovery-from-boot patch and writes the script to output.
 
-  recovery_img and boot_img should be File objects for the
-  corresponding images.  info should be the dictionary returned by
-  common.LoadInfoDict() on the input target_files.
+  Most of the space in the boot and recovery images is just the kernel, which is
+  identical for the two, so the resulting patch should be efficient. Add it to
+  the output zip, along with a shell script that is run from init.rc on first
+  boot to actually do the patching and install the new recovery image.
+
+  Args:
+    input_dir: The top-level input directory of the target-files.zip.
+    output_sink: The callback function that writes the result.
+    recovery_img: File object for the recovery image.
+    boot_img: File objects for the boot image.
+    info_dict: A dict returned by common.LoadInfoDict() on the input
+        target_files. Will use OPTIONS.info_dict if None has been given.
   """
-
   if info_dict is None:
     info_dict = OPTIONS.info_dict
 
-  full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
+  full_recovery_image = info_dict.get("full_recovery_image") == "true"
 
   if full_recovery_image:
     output_sink("etc/recovery.img", recovery_img.data)
 
   else:
-    diff_program = ["imgdiff"]
+    system_root_image = info_dict.get("system_root_image") == "true"
     path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
-    if os.path.exists(path):
-      diff_program.append("-b")
-      diff_program.append(path)
-      bonus_args = "-b /system/etc/recovery-resource.dat"
-    else:
+    # With system-root-image, boot and recovery images will have mismatching
+    # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
+    # to handle such a case.
+    if system_root_image:
+      diff_program = ["bsdiff"]
       bonus_args = ""
+      assert not os.path.exists(path)
+    else:
+      diff_program = ["imgdiff"]
+      if os.path.exists(path):
+        diff_program.append("-b")
+        diff_program.append(path)
+        bonus_args = "-b /system/etc/recovery-resource.dat"
+      else:
+        bonus_args = ""
 
     d = Difference(recovery_img, boot_img, diff_program=diff_program)
     _, _, patch = d.ComputePatch()
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 3579645..1e2f39e 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1024,6 +1024,9 @@
     """
     return self._GetPropertyFilesString(input_zip, reserve_space=True)
 
+  class InsufficientSpaceException(Exception):
+    pass
+
   def Finalize(self, input_zip, reserved_length):
     """Finalizes a property-files string with actual METADATA offset/size info.
 
@@ -1045,13 +1048,15 @@
       "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379  ".
 
     Raises:
-      AssertionError: If the reserved length is insufficient to hold the final
-          string.
+      InsufficientSpaceException: If the reserved length is insufficient to hold
+          the final string.
     """
     result = self._GetPropertyFilesString(input_zip, reserve_space=False)
-    assert len(result) <= reserved_length, \
-        'Insufficient reserved space: reserved={}, actual={}'.format(
-            reserved_length, len(result))
+    if len(result) > reserved_length:
+      raise self.InsufficientSpaceException(
+          'Insufficient reserved space: reserved={}, actual={}'.format(
+              reserved_length, len(result)))
+
     result += ' ' * (reserved_length - len(result))
     return result
 
@@ -1089,11 +1094,12 @@
 
     # 'META-INF/com/android/metadata' is required. We don't know its actual
     # offset and length (as well as the values for other entries). So we reserve
-    # 10-byte as a placeholder, which is to cover the space for metadata entry
-    # ('xx:xxx', since it's ZIP_STORED which should appear at the beginning of
-    # the zip), as well as the possible value changes in other entries.
+    # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
+    # the space for metadata entry. Because 'offset' allows a max of 10-digit
+    # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
+    # reserved space serves the metadata entry only.
     if reserve_space:
-      tokens.append('metadata:' + ' ' * 10)
+      tokens.append('metadata:' + ' ' * 15)
     else:
       tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
 
@@ -1252,36 +1258,54 @@
     output_file: The final output ZIP filename.
     needed_property_files: The list of PropertyFiles' to be generated.
   """
-  output_zip = zipfile.ZipFile(
-      input_file, 'a', compression=zipfile.ZIP_DEFLATED)
 
-  # Write the current metadata entry with placeholders.
-  for property_files in needed_property_files:
-    metadata[property_files.name] = property_files.Compute(output_zip)
-  WriteMetadata(metadata, output_zip)
-  common.ZipClose(output_zip)
+  def ComputeAllPropertyFiles(input_file, needed_property_files):
+    # Write the current metadata entry with placeholders.
+    with zipfile.ZipFile(input_file) as input_zip:
+      for property_files in needed_property_files:
+        metadata[property_files.name] = property_files.Compute(input_zip)
+      namelist = input_zip.namelist()
 
-  # SignOutput(), which in turn calls signapk.jar, will possibly reorder the
-  # ZIP entries, as well as padding the entry headers. We do a preliminary
-  # signing (with an incomplete metadata entry) to allow that to happen. Then
-  # compute the ZIP entry offsets, write back the final metadata and do the
-  # final signing.
-  if OPTIONS.no_signing:
-    prelim_signing = input_file
-  else:
+    if METADATA_NAME in namelist:
+      common.ZipDelete(input_file, METADATA_NAME)
+    output_zip = zipfile.ZipFile(input_file, 'a')
+    WriteMetadata(metadata, output_zip)
+    common.ZipClose(output_zip)
+
+    if OPTIONS.no_signing:
+      return input_file
+
     prelim_signing = common.MakeTempFile(suffix='.zip')
     SignOutput(input_file, prelim_signing)
+    return prelim_signing
 
-  # Open the signed zip. Compute the final metadata that's needed for streaming.
-  with zipfile.ZipFile(prelim_signing, 'r') as prelim_signing_zip:
-    for property_files in needed_property_files:
-      metadata[property_files.name] = property_files.Finalize(
-          prelim_signing_zip, len(metadata[property_files.name]))
+  def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
+    with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
+      for property_files in needed_property_files:
+        metadata[property_files.name] = property_files.Finalize(
+            prelim_signing_zip, len(metadata[property_files.name]))
+
+  # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
+  # entries, as well as padding the entry headers. We do a preliminary signing
+  # (with an incomplete metadata entry) to allow that to happen. Then compute
+  # the ZIP entry offsets, write back the final metadata and do the final
+  # signing.
+  prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
+  try:
+    FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
+  except PropertyFiles.InsufficientSpaceException:
+    # Even with the preliminary signing, the entry orders may change
+    # dramatically, which leads to insufficiently reserved space during the
+    # first call to ComputeAllPropertyFiles(). In that case, we redo all the
+    # preliminary signing works, based on the already ordered ZIP entries, to
+    # address the issue.
+    prelim_signing = ComputeAllPropertyFiles(
+        prelim_signing, needed_property_files)
+    FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
 
   # Replace the METADATA entry.
   common.ZipDelete(prelim_signing, METADATA_NAME)
-  output_zip = zipfile.ZipFile(
-      prelim_signing, 'a', compression=zipfile.ZIP_DEFLATED)
+  output_zip = zipfile.ZipFile(prelim_signing, 'a')
   WriteMetadata(metadata, output_zip)
   common.ZipClose(output_zip)
 
@@ -1292,7 +1316,7 @@
     SignOutput(prelim_signing, output_file)
 
   # Reopen the final signed zip to double check the streaming metadata.
-  with zipfile.ZipFile(output_file, 'r') as output_zip:
+  with zipfile.ZipFile(output_file) as output_zip:
     for property_files in needed_property_files:
       property_files.Verify(output_zip, metadata[property_files.name].strip())
 
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 97687e7..d7cace8 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -24,8 +24,8 @@
 import common
 import test_utils
 from ota_from_target_files import (
-    _LoadOemDicts, AbOtaPropertyFiles, BuildInfo, GetPackageMetadata,
-    GetTargetFilesZipForSecondaryImages,
+    _LoadOemDicts, AbOtaPropertyFiles, BuildInfo, FinalizeMetadata,
+    GetPackageMetadata, GetTargetFilesZipForSecondaryImages,
     GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles,
     Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
     StreamingPropertyFiles, WriteFingerprintAssertion)
@@ -373,11 +373,22 @@
   }
 
   def setUp(self):
+    self.testdata_dir = test_utils.get_testdata_dir()
+    self.assertTrue(os.path.exists(self.testdata_dir))
+
     # Reset the global options as in ota_from_target_files.py.
     common.OPTIONS.incremental_source = None
     common.OPTIONS.downgrade = False
     common.OPTIONS.timestamp = False
     common.OPTIONS.wipe_user_data = False
+    common.OPTIONS.no_signing = False
+    common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
+    common.OPTIONS.key_passwords = {
+        common.OPTIONS.package_key : None,
+    }
+
+    common.OPTIONS.search_path = test_utils.get_search_path()
+    self.assertIsNotNone(common.OPTIONS.search_path)
 
   def tearDown(self):
     common.Cleanup()
@@ -590,6 +601,68 @@
     with zipfile.ZipFile(target_file) as verify_zip:
       self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
 
+  def _test_FinalizeMetadata(self, large_entry=False):
+    entries = [
+        'required-entry1',
+        'required-entry2',
+    ]
+    zip_file = PropertyFilesTest.construct_zip_package(entries)
+    # Add a large entry of 1 GiB if requested.
+    if large_entry:
+      with zipfile.ZipFile(zip_file, 'a') as zip_fp:
+        zip_fp.writestr(
+            # Using 'zoo' so that the entry stays behind others after signing.
+            'zoo',
+            'A' * 1024 * 1024 * 1024,
+            zipfile.ZIP_STORED)
+
+    metadata = {}
+    output_file = common.MakeTempFile(suffix='.zip')
+    needed_property_files = (
+        TestPropertyFiles(),
+    )
+    FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
+    self.assertIn('ota-test-property-files', metadata)
+
+  def test_FinalizeMetadata(self):
+    self._test_FinalizeMetadata()
+
+  def test_FinalizeMetadata_withNoSigning(self):
+    common.OPTIONS.no_signing = True
+    self._test_FinalizeMetadata()
+
+  def test_FinalizeMetadata_largeEntry(self):
+    self._test_FinalizeMetadata(large_entry=True)
+
+  def test_FinalizeMetadata_largeEntry_withNoSigning(self):
+    common.OPTIONS.no_signing = True
+    self._test_FinalizeMetadata(large_entry=True)
+
+  def test_FinalizeMetadata_insufficientSpace(self):
+    entries = [
+        'required-entry1',
+        'required-entry2',
+        'optional-entry1',
+        'optional-entry2',
+    ]
+    zip_file = PropertyFilesTest.construct_zip_package(entries)
+    with zipfile.ZipFile(zip_file, 'a') as zip_fp:
+      zip_fp.writestr(
+          # 'foo-entry1' will appear ahead of all other entries (in alphabetical
+          # order) after the signing, which will in turn trigger the
+          # InsufficientSpaceException and an automatic retry.
+          'foo-entry1',
+          'A' * 1024 * 1024,
+          zipfile.ZIP_STORED)
+
+    metadata = {}
+    needed_property_files = (
+        TestPropertyFiles(),
+    )
+    output_file = common.MakeTempFile(suffix='.zip')
+    FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
+    self.assertIn('ota-test-property-files', metadata)
+
 
 class TestPropertyFiles(PropertyFiles):
   """A class that extends PropertyFiles for testing purpose."""
@@ -609,11 +682,14 @@
 
 class PropertyFilesTest(unittest.TestCase):
 
+  def setUp(self):
+    common.OPTIONS.no_signing = False
+
   def tearDown(self):
     common.Cleanup()
 
   @staticmethod
-  def _construct_zip_package(entries):
+  def construct_zip_package(entries):
     zip_file = common.MakeTempFile(suffix='.zip')
     with zipfile.ZipFile(zip_file, 'w') as zip_fp:
       for entry in entries:
@@ -647,7 +723,7 @@
         'required-entry1',
         'required-entry2',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       property_files_string = property_files.Compute(zip_fp)
@@ -663,7 +739,7 @@
         'optional-entry1',
         'optional-entry2',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       property_files_string = property_files.Compute(zip_fp)
@@ -676,7 +752,7 @@
     entries = (
         'required-entry2',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       self.assertRaises(KeyError, property_files.Compute, zip_fp)
@@ -687,7 +763,7 @@
         'required-entry2',
         'META-INF/com/android/metadata',
     ]
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # pylint: disable=protected-access
@@ -710,7 +786,7 @@
         'optional-entry2',
         'META-INF/com/android/metadata',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # First get the raw metadata string (i.e. without padding space).
@@ -725,7 +801,7 @@
 
       # Or pass in insufficient length.
       self.assertRaises(
-          AssertionError,
+          PropertyFiles.InsufficientSpaceException,
           property_files.Finalize,
           zip_fp,
           raw_length - 1)
@@ -745,7 +821,7 @@
         'optional-entry2',
         'META-INF/com/android/metadata',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = TestPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # First get the raw metadata string (i.e. without padding space).
@@ -787,7 +863,7 @@
         'care_map.txt',
         'compatibility.zip',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = StreamingPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       property_files_string = property_files.Compute(zip_fp)
@@ -804,7 +880,7 @@
         'compatibility.zip',
         'META-INF/com/android/metadata',
     ]
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = StreamingPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # pylint: disable=protected-access
@@ -827,7 +903,7 @@
         'compatibility.zip',
         'META-INF/com/android/metadata',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = StreamingPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # First get the raw metadata string (i.e. without padding space).
@@ -923,8 +999,8 @@
       self.assertEqual(verify_fp.read(), metadata_signature)
 
   @staticmethod
-  def _construct_zip_package_withValidPayload(with_metadata=False):
-    # Cannot use _construct_zip_package() since we need a "valid" payload.bin.
+  def construct_zip_package_withValidPayload(with_metadata=False):
+    # Cannot use construct_zip_package() since we need a "valid" payload.bin.
     target_file = construct_target_files()
     payload = Payload()
     payload.Generate(target_file)
@@ -951,7 +1027,7 @@
     return zip_file
 
   def test_Compute(self):
-    zip_file = self._construct_zip_package_withValidPayload()
+    zip_file = self.construct_zip_package_withValidPayload()
     property_files = AbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       property_files_string = property_files.Compute(zip_fp)
@@ -964,7 +1040,7 @@
         zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
 
   def test_Finalize(self):
-    zip_file = self._construct_zip_package_withValidPayload(with_metadata=True)
+    zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
     property_files = AbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # pylint: disable=protected-access
@@ -980,7 +1056,7 @@
         zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
 
   def test_Verify(self):
-    zip_file = self._construct_zip_package_withValidPayload(with_metadata=True)
+    zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
     property_files = AbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       # pylint: disable=protected-access
@@ -1001,7 +1077,7 @@
 
   def test_Compute(self):
     entries = ()
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = NonAbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file) as zip_fp:
       property_files_string = property_files.Compute(zip_fp)
@@ -1014,7 +1090,7 @@
     entries = [
         'META-INF/com/android/metadata',
     ]
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = NonAbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file) as zip_fp:
       # pylint: disable=protected-access
@@ -1032,7 +1108,7 @@
     entries = (
         'META-INF/com/android/metadata',
     )
-    zip_file = self._construct_zip_package(entries)
+    zip_file = self.construct_zip_package(entries)
     property_files = NonAbOtaPropertyFiles()
     with zipfile.ZipFile(zip_file) as zip_fp:
       # pylint: disable=protected-access
diff --git a/tools/releasetools/test_utils.py b/tools/releasetools/test_utils.py
index e64355b..a15ff5b 100644
--- a/tools/releasetools/test_utils.py
+++ b/tools/releasetools/test_utils.py
@@ -32,6 +32,22 @@
   return os.path.join(current_dir, 'testdata')
 
 
+def get_search_path():
+  """Returns the search path that has 'framework/signapk.jar' under."""
+  current_dir = os.path.dirname(os.path.realpath(__file__))
+  for path in (
+      # In relative to 'build/make/tools/releasetools' in the Android source.
+      ['..'] * 4 + ['out', 'host', 'linux-x86'],
+      # Or running the script unpacked from otatools.zip.
+      ['..']):
+    full_path = os.path.realpath(os.path.join(current_dir, *path))
+    signapk_path = os.path.realpath(
+        os.path.join(full_path, 'framework', 'signapk.jar'))
+    if os.path.exists(signapk_path):
+      return full_path
+  return None
+
+
 def construct_sparse_image(chunks):
   """Returns a sparse image file constructed from the given chunks.