Generate partition timestamps in ota_from_target_files

Test: make an OTA package
Bug: 162553432
Change-Id: I17b9f1b24307255b1e5115de12fa516126b32365
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 56785d6..c77d8c6 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -118,7 +118,7 @@
 AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
 
 # Partitions that should have their care_map added to META/care_map.pb
-PARTITIONS_WITH_CARE_MAP = (
+PARTITIONS_WITH_CARE_MAP = [
     'system',
     'vendor',
     'product',
@@ -126,7 +126,7 @@
     'odm',
     'vendor_dlkm',
     'odm_dlkm',
-)
+]
 
 
 class ErrorCode(object):
@@ -729,10 +729,14 @@
       fingerprint = build_info.GetPartitionFingerprint(partition)
       if fingerprint:
         d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
-
+  try:
+    d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
+  except KeyError:
+    logger.warning("Can't find META/ab_partitions.txt")
   return d
 
 
+
 def LoadListFromFile(file_path):
   with open(file_path) as f:
     return f.read().splitlines()
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 93a3e0e..2833397 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -778,7 +778,7 @@
   with open(new_ab_partitions, 'w') as f:
     for partition in ab_partitions:
       if (partition in dynamic_partition_list and
-          partition not in super_block_devices):
+              partition not in super_block_devices):
         logger.info("Dropping %s from ab_partitions.txt", partition)
         continue
       f.write(partition + "\n")
@@ -825,31 +825,49 @@
                                compression=zipfile.ZIP_DEFLATED)
 
   if source_file is not None:
+    assert "ab_partitions" in OPTIONS.source_info_dict, \
+        "META/ab_partitions.txt is required for ab_update."
+    assert "ab_partitions" in OPTIONS.target_info_dict, \
+        "META/ab_partitions.txt is required for ab_update."
     target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
     source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
   else:
+    assert "ab_partitions" in OPTIONS.info_dict, \
+        "META/ab_partitions.txt is required for ab_update."
     target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
     source_info = None
 
-  # Metadata to comply with Android OTA package format.
-  metadata = GetPackageMetadata(target_info, source_info)
-
   if OPTIONS.retrofit_dynamic_partitions:
     target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
         target_file, target_info.get("super_block_devices").strip().split(),
         target_info.get("dynamic_partition_list").strip().split())
   elif OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
+  # Target_file may have been modified, reparse ab_partitions
+  with zipfile.ZipFile(target_file, allowZip64=True) as zfp:
+    target_info.info_dict['ab_partitions'] = zfp.read(
+        AB_PARTITIONS).strip().split("\n")
 
+  # Metadata to comply with Android OTA package format.
+  metadata = GetPackageMetadata(target_info, source_info)
   # Generate payload.
   payload = Payload()
 
+  partition_timestamps = []
   # Enforce a max timestamp this payload can be applied on top of.
   if OPTIONS.downgrade:
     max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
   else:
     max_timestamp = str(metadata.postcondition.timestamp)
+    partition_timestamps = [
+        part.partition_name + ":" + part.version
+        for part in metadata.postcondition.partition_state]
   additional_args = ["--max_timestamp", max_timestamp]
+  if partition_timestamps:
+    additional_args.extend(
+        ["--partition_timestamps", ",".join(
+            partition_timestamps)]
+    )
 
   payload.Generate(target_file, source_file, additional_args)
 
@@ -877,7 +895,7 @@
   # into A/B OTA package.
   target_zip = zipfile.ZipFile(target_file, "r")
   if (target_info.get("verity") == "true" or
-      target_info.get("avb_enable") == "true"):
+          target_info.get("avb_enable") == "true"):
     care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
                      "META/" + x in target_zip.namelist()]
 
@@ -1073,7 +1091,7 @@
   # use_dynamic_partitions but target build does.
   if (OPTIONS.source_info_dict and
       OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
-      OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
+          OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
     if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
       raise common.ExternalError(
           "Expect to generate incremental OTA for retrofitting dynamic "
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 2e26a05..d444d41 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -162,10 +162,18 @@
 
   def UpdatePartitionStates(partition_states):
     """Update the per-partition state according to its build.prop"""
-
+    if not build_info.is_ab:
+      return
     build_info_set = ComputeRuntimeBuildInfos(build_info,
                                               boot_variable_values)
-    for partition in PARTITIONS_WITH_CARE_MAP:
+    assert "ab_partitions" in build_info.info_dict,\
+      "ab_partitions property required for ab update."
+    ab_partitions = set(build_info.info_dict.get("ab_partitions"))
+
+    # delta_generator will error out on unused timestamps,
+    # so only generate timestamps for dynamic partitions
+    # used in OTA update.
+    for partition in sorted(set(PARTITIONS_WITH_CARE_MAP) & ab_partitions):
       partition_prop = build_info.info_dict.get(
           '{}.build.prop'.format(partition))
       # Skip if the partition is missing, or it doesn't have a build.prop
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 6f5e78f..84cd4c8 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -30,7 +30,7 @@
     GetTargetFilesZipForSecondaryImages,
     GetTargetFilesZipWithoutPostinstallConfig,
     Payload, PayloadSigner, POSTINSTALL_CONFIG,
-    StreamingPropertyFiles)
+    StreamingPropertyFiles, AB_PARTITIONS)
 from test_utils import PropertyFilesTestCase
 
 
@@ -166,7 +166,7 @@
     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.package_key: None,
     }
 
     common.OPTIONS.search_path = test_utils.get_search_path()
@@ -179,40 +179,42 @@
   def test_GetPackageMetadata_abOta_full(self):
     target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
     target_info_dict['ab_update'] = 'true'
+    target_info_dict['ab_partitions'] = []
     target_info = common.BuildInfo(target_info_dict, None)
     metadata = self.GetLegacyOtaMetadata(target_info)
     self.assertDictEqual(
         {
-            'ota-type' : 'AB',
-            'ota-required-cache' : '0',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
+            'ota-type': 'AB',
+            'ota-required-cache': '0',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
         },
         metadata)
 
   def test_GetPackageMetadata_abOta_incremental(self):
     target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
     target_info_dict['ab_update'] = 'true'
+    target_info_dict['ab_partitions'] = []
     target_info = common.BuildInfo(target_info_dict, None)
     source_info = common.BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
     common.OPTIONS.incremental_source = ''
     metadata = self.GetLegacyOtaMetadata(target_info, source_info)
     self.assertDictEqual(
         {
-            'ota-type' : 'AB',
-            'ota-required-cache' : '0',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
-            'pre-build' : 'build-fingerprint-source',
-            'pre-build-incremental' : 'build-version-incremental-source',
+            'ota-type': 'AB',
+            'ota-required-cache': '0',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
+            'pre-build': 'build-fingerprint-source',
+            'pre-build-incremental': 'build-version-incremental-source',
         },
         metadata)
 
@@ -221,14 +223,14 @@
     metadata = self.GetLegacyOtaMetadata(target_info)
     self.assertDictEqual(
         {
-            'ota-type' : 'BLOCK',
-            'ota-required-cache' : '0',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
+            'ota-type': 'BLOCK',
+            'ota-required-cache': '0',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
         },
         metadata)
 
@@ -239,16 +241,16 @@
     metadata = self.GetLegacyOtaMetadata(target_info, source_info)
     self.assertDictEqual(
         {
-            'ota-type' : 'BLOCK',
-            'ota-required-cache' : '0',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
-            'pre-build' : 'build-fingerprint-source',
-            'pre-build-incremental' : 'build-version-incremental-source',
+            'ota-type': 'BLOCK',
+            'ota-required-cache': '0',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
+            'pre-build': 'build-fingerprint-source',
+            'pre-build-incremental': 'build-version-incremental-source',
         },
         metadata)
 
@@ -258,15 +260,15 @@
     metadata = self.GetLegacyOtaMetadata(target_info)
     self.assertDictEqual(
         {
-            'ota-type' : 'BLOCK',
-            'ota-required-cache' : '0',
-            'ota-wipe' : 'yes',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
+            'ota-type': 'BLOCK',
+            'ota-required-cache': '0',
+            'ota-wipe': 'yes',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
         },
         metadata)
 
@@ -276,15 +278,15 @@
     metadata = self.GetLegacyOtaMetadata(target_info)
     self.assertDictEqual(
         {
-            'ota-retrofit-dynamic-partitions' : 'yes',
-            'ota-type' : 'BLOCK',
-            'ota-required-cache' : '0',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1500000000',
-            'pre-device' : 'product-device',
+            'ota-retrofit-dynamic-partitions': 'yes',
+            'ota-type': 'BLOCK',
+            'ota-required-cache': '0',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
         },
         metadata)
 
@@ -322,18 +324,18 @@
 
     self.assertDictEqual(
         {
-            'ota-downgrade' : 'yes',
-            'ota-type' : 'BLOCK',
-            'ota-required-cache' : '0',
-            'ota-wipe' : 'yes',
-            'post-build' : 'build-fingerprint-target',
-            'post-build-incremental' : 'build-version-incremental-target',
-            'post-sdk-level' : '27',
-            'post-security-patch-level' : '2017-12-01',
-            'post-timestamp' : '1400000000',
-            'pre-device' : 'product-device',
-            'pre-build' : 'build-fingerprint-source',
-            'pre-build-incremental' : 'build-version-incremental-source',
+            'ota-downgrade': 'yes',
+            'ota-type': 'BLOCK',
+            'ota-required-cache': '0',
+            'ota-wipe': 'yes',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1400000000',
+            'pre-device': 'product-device',
+            'pre-build': 'build-fingerprint-source',
+            'pre-build-incremental': 'build-version-incremental-source',
         },
         metadata)
 
@@ -770,7 +772,7 @@
     common.OPTIONS.payload_signer_args = None
     common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
     common.OPTIONS.key_passwords = {
-        common.OPTIONS.package_key : None,
+        common.OPTIONS.package_key: None,
     }
 
   def test_init(self):
@@ -904,7 +906,8 @@
     with zipfile.ZipFile(zip_file, 'r') as zip_fp:
       raw_metadata = property_files.GetPropertyFilesString(
           zip_fp, reserve_space=False)
-      property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
+      property_files_string = property_files.Finalize(
+          zip_fp, len(raw_metadata))
 
     tokens = self._parse_property_files_string(property_files_string)
     # "7" includes the four entries above, two metadata entries, and one entry
@@ -937,7 +940,7 @@
     common.OPTIONS.payload_signer_args = []
     common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
     common.OPTIONS.key_passwords = {
-        common.OPTIONS.package_key : None,
+        common.OPTIONS.package_key: None,
     }
 
   def _assertFilesEqual(self, file1, file2):
@@ -955,7 +958,7 @@
     common.OPTIONS.package_key = os.path.join(
         self.testdata_dir, 'testkey_with_passwd')
     common.OPTIONS.key_passwords = {
-        common.OPTIONS.package_key : 'foo',
+        common.OPTIONS.package_key: 'foo',
     }
     payload_signer = PayloadSigner()
     self.assertEqual('openssl', payload_signer.signer)
@@ -1032,7 +1035,7 @@
     common.OPTIONS.payload_signer_args = None
     common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
     common.OPTIONS.key_passwords = {
-        common.OPTIONS.package_key : None,
+        common.OPTIONS.package_key: None,
     }
 
   @staticmethod
@@ -1187,8 +1190,8 @@
       # Then assert these entries are stored.
       for entry_info in verify_zip.infolist():
         if entry_info.filename not in (
-            Payload.SECONDARY_PAYLOAD_BIN,
-            Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
+                Payload.SECONDARY_PAYLOAD_BIN,
+                Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
           continue
         self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
 
@@ -1198,6 +1201,7 @@
       'recovery_api_version=3',
       'fstab_version=2',
       'recovery_as_boot=true',
+      'ab_update=true',
   ]
 
   BUILD_PROP = [
@@ -1359,6 +1363,7 @@
         'ro.product.vendor.name=vendor-product-std',
         'VENDOR/etc/build_pro.prop':
         'ro.product.vendor.name=vendor-product-pro',
+        AB_PARTITIONS: '\n'.join(['system', 'vendor']),
     }, self.test_dir)
 
     common.OPTIONS.boot_variable_file = common.MakeTempFile()
@@ -1410,6 +1415,8 @@
         'import /vendor/etc/build_${ro.boot.sku_name}.prop',
     ])
     self.writeFiles({
+        'META/misc_info.txt': '\n'.join(self.MISC_INFO),
+        'META/ab_partitions.txt': '\n'.join(['system', 'vendor', 'product']),
         'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP),
         'VENDOR/build.prop': '\n'.join(vendor_build_prop),
         'VENDOR/etc/build_std.prop':
@@ -1446,6 +1453,7 @@
     ]
     self.writeFiles({
         'META/misc_info.txt': '\n'.join(self.MISC_INFO),
+        'META/ab_partitions.txt': '\n'.join(['system', 'vendor', 'product']),
         'SYSTEM/build.prop': '\n'.join(source_build_prop),
         'VENDOR/build.prop': '\n'.join(vendor_build_prop),
         'VENDOR/etc/build_std.prop':