releasetools: Add support for compressed APKs.

Compressed APKs can be identified by a "compressed=<ext>" entry in
the apkcerts.txt file. When we encounter such an entry, we need to
decompress the file to a temporary location before we process its
certs. When we're signing, we should also recompress the package
after it's signed.

Bug: 64531948
Test: ./build/tools/releasetools/check_target_files_signatures.py
Test: ./build/tools/releasetools/sign_target_files_apks.py
Test: compared signed output before / after this change, verify that
      it's bitwise identical when no compressed APKs are present.

Change-Id: Id32e52f9c11023955330c113117daaf6b73bd8c2
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index ce57f62..9d58954 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -18,6 +18,7 @@
 import errno
 import getopt
 import getpass
+import gzip
 import imp
 import os
 import platform
@@ -552,6 +553,13 @@
   return None
 
 
+def Gunzip(in_filename, out_filename):
+  """Gunzip the given gzip compressed file to a given output file.
+  """
+  with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
+    shutil.copyfileobj(in_file, out_file)
+
+
 def UnzipTemp(filename, pattern=None):
   """Unzip the given archive into a temporary directory and return the name.
 
@@ -757,16 +765,26 @@
 
 def ReadApkCerts(tf_zip):
   """Given a target_files ZipFile, parse the META/apkcerts.txt file
-  and return a {package: cert} dict."""
+  and return a tuple with the following elements: (1) a dictionary that maps
+  packages to certs (based on the "certificate" and "private_key" attributes
+  in the file. (2) A string representing the extension of compressed APKs in
+  the target files (e.g ".gz" ".bro")."""
   certmap = {}
+  compressed_extension = None
+
   for line in tf_zip.read("META/apkcerts.txt").split("\n"):
     line = line.strip()
     if not line:
       continue
-    m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
-                 r'private_key="(.*)"$', line)
+    m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
+                 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
+                 line)
     if m:
-      name, cert, privkey = m.groups()
+      matches = m.groupdict()
+      cert = matches["CERT"]
+      privkey = matches["PRIVKEY"]
+      name = matches["NAME"]
+      this_compressed_extension = matches["COMPRESSED"]
       public_key_suffix_len = len(OPTIONS.public_key_suffix)
       private_key_suffix_len = len(OPTIONS.private_key_suffix)
       if cert in SPECIAL_CERT_STRINGS and not privkey:
@@ -777,7 +795,18 @@
         certmap[name] = cert[:-public_key_suffix_len]
       else:
         raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
-  return certmap
+      if this_compressed_extension:
+        # Make sure that all the values in the compression map have the same
+        # extension. We don't support multiple compression methods in the same
+        # system image.
+        if compressed_extension:
+          if this_compressed_extension != compressed_extension:
+            raise ValueError("multiple compressed extensions : %s vs %s",
+                             (compressed_extension, this_compressed_extension))
+        else:
+          compressed_extension = this_compressed_extension
+
+  return (certmap, ("." + compressed_extension) if compressed_extension else None)
 
 
 COMMON_DOCSTRING = """