releasetools: Create PropertyFiles class.

And move StreamingPropertyFiles as its subclass. We will need similar
PropertyFiles instance for non-A/B OTA as well (to expose the
offset/size for the METADATA entry).

Bug: 74210298
Test: python -m unittest test_ota_from_target_files
Test: Generate an A/B OTA. Check the generated property-files string.
Test: pylint --rcfile=pylintrc \
          ota_from_target_files.py \
          test_ota_from_target_files.py
Change-Id: If90d97f0b330749fd8a6cde2ed9d0d6cd6ea60a8
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 3a0a788..7d266f6 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -955,8 +955,15 @@
   return metadata
 
 
-class StreamingPropertyFiles(object):
-  """Computes the ota-streaming-property-files string for streaming A/B OTA.
+class PropertyFiles(object):
+  """A class that computes the property-files string for an OTA package.
+
+  A property-files string is a comma-separated string that contains the
+  offset/size info for an OTA package. The entries, which must be ZIP_STORED,
+  can be fetched directly with the package URL along with the offset/size info.
+  These strings can be used for streaming A/B OTAs, or allowing an updater to
+  download package metadata entry directly, without paying the cost of
+  downloading entire package.
 
   Computing the final property-files string requires two passes. Because doing
   the whole package signing (with signapk.jar) will possibly reorder the ZIP
@@ -966,7 +973,7 @@
   This class provides functions to be called for each pass. The general flow is
   as follows.
 
-    property_files = StreamingPropertyFiles()
+    property_files = PropertyFiles()
     # The first pass, which writes placeholders before doing initial signing.
     property_files.Compute()
     SignOutput()
@@ -981,17 +988,9 @@
   """
 
   def __init__(self):
-    self.required = (
-        # payload.bin and payload_properties.txt must exist.
-        'payload.bin',
-        'payload_properties.txt',
-    )
-    self.optional = (
-        # care_map.txt is available only if dm-verity is enabled.
-        'care_map.txt',
-        # compatibility.zip is available only if target supports Treble.
-        'compatibility.zip',
-    )
+    self.name = None
+    self.required = ()
+    self.optional = ()
 
   def Compute(self, input_zip):
     """Computes and returns a property-files string with placeholders.
@@ -1083,7 +1082,26 @@
     return ','.join(tokens)
 
 
-def FinalizeMetadata(metadata, input_file, output_file):
+class StreamingPropertyFiles(PropertyFiles):
+  """A subclass for computing the property-files for streaming A/B OTAs."""
+
+  def __init__(self):
+    super(StreamingPropertyFiles, self).__init__()
+    self.name = 'ota-streaming-property-files'
+    self.required = (
+        # payload.bin and payload_properties.txt must exist.
+        'payload.bin',
+        'payload_properties.txt',
+    )
+    self.optional = (
+        # care_map.txt is available only if dm-verity is enabled.
+        'care_map.txt',
+        # compatibility.zip is available only if target supports Treble.
+        'compatibility.zip',
+    )
+
+
+def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
   """Finalizes the metadata and signs an A/B OTA package.
 
   In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
@@ -1101,14 +1119,14 @@
     input_file: The input ZIP filename that doesn't contain the package METADATA
         entry yet.
     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)
 
-  property_files = StreamingPropertyFiles()
-
   # Write the current metadata entry with placeholders.
-  metadata['ota-streaming-property-files'] = property_files.Compute(output_zip)
+  for property_files in needed_property_files:
+    metadata[property_files.name] = property_files.Compute(output_zip)
   WriteMetadata(metadata, output_zip)
   common.ZipClose(output_zip)
 
@@ -1122,14 +1140,14 @@
 
   # Open the signed zip. Compute the final metadata that's needed for streaming.
   with zipfile.ZipFile(prelim_signing, 'r') as prelim_signing_zip:
-    expected_length = len(metadata['ota-streaming-property-files'])
-    metadata['ota-streaming-property-files'] = property_files.Finalize(
-        prelim_signing_zip, expected_length)
+    for property_files in needed_property_files:
+      metadata[property_files.name] = property_files.Finalize(
+          prelim_signing_zip, len(metadata[property_files.name]))
 
   # 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', compression=zipfile.ZIP_DEFLATED)
   WriteMetadata(metadata, output_zip)
   common.ZipClose(output_zip)
 
@@ -1138,8 +1156,8 @@
 
   # Reopen the final signed zip to double check the streaming metadata.
   with zipfile.ZipFile(output_file, 'r') as output_zip:
-    property_files.Verify(
-        output_zip, metadata['ota-streaming-property-files'].strip())
+    for property_files in needed_property_files:
+      property_files.Verify(output_zip, metadata[property_files.name].strip())
 
 
 def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
@@ -1555,7 +1573,10 @@
   # FinalizeMetadata().
   common.ZipClose(output_zip)
 
-  FinalizeMetadata(metadata, staging_file, output_file)
+  needed_property_files = (
+      StreamingPropertyFiles(),
+  )
+  FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
 
 
 def main(argv):