Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2008 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Signs all the APK files in a target-files zipfile, producing a new
     19 target-files zip.
     20 
     21 Usage:  sign_target_files_apks [flags] input_target_files output_target_files
     22 
     23   -e  (--extra_apks)  <name,name,...=key>
     24       Add extra APK name/key pairs as though they appeared in
     25       apkcerts.txt (so mappings specified by -k and -d are applied).
     26       Keys specified in -e override any value for that app contained
     27       in the apkcerts.txt file.  Option may be repeated to give
     28       multiple extra packages.
     29 
     30   -k  (--key_mapping)  <src_key=dest_key>
     31       Add a mapping from the key name as specified in apkcerts.txt (the
     32       src_key) to the real key you wish to sign the package with
     33       (dest_key).  Option may be repeated to give multiple key
     34       mappings.
     35 
     36   -d  (--default_key_mappings)  <dir>
     37       Set up the following key mappings:
     38 
     39         $devkey/devkey    ==>  $dir/releasekey
     40         $devkey/testkey   ==>  $dir/releasekey
     41         $devkey/media     ==>  $dir/media
     42         $devkey/shared    ==>  $dir/shared
     43         $devkey/platform  ==>  $dir/platform
     44 
     45       where $devkey is the directory part of the value of
     46       default_system_dev_certificate from the input target-files's
     47       META/misc_info.txt.  (Defaulting to "build/target/product/security"
     48       if the value is not present in misc_info.
     49 
     50       -d and -k options are added to the set of mappings in the order
     51       in which they appear on the command line.
     52 
     53   -o  (--replace_ota_keys)
     54       Replace the certificate (public key) used by OTA package
     55       verification with the one specified in the input target_files
     56       zip (in the META/otakeys.txt file).  Key remapping (-k and -d)
     57       is performed on this key.
     58 
     59   -t  (--tag_changes)  <+tag>,<-tag>,...
     60       Comma-separated list of changes to make to the set of tags (in
     61       the last component of the build fingerprint).  Prefix each with
     62       '+' or '-' to indicate whether that tag should be added or
     63       removed.  Changes are processed in the order they appear.
     64       Default value is "-test-keys,-dev-keys,+release-keys".
     65 
     66 """
     67 
     68 import sys
     69 
     70 if sys.hexversion < 0x02070000:
     71   print >> sys.stderr, "Python 2.7 or newer is required."
     72   sys.exit(1)
     73 
     74 import base64
     75 import cStringIO
     76 import copy
     77 import errno
     78 import os
     79 import re
     80 import shutil
     81 import subprocess
     82 import tempfile
     83 import zipfile
     84 
     85 import add_img_to_target_files
     86 import common
     87 
     88 OPTIONS = common.OPTIONS
     89 
     90 OPTIONS.extra_apks = {}
     91 OPTIONS.key_map = {}
     92 OPTIONS.replace_ota_keys = False
     93 OPTIONS.replace_verity_public_key = False
     94 OPTIONS.replace_verity_private_key = False
     95 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
     96 
     97 def GetApkCerts(tf_zip):
     98   certmap = common.ReadApkCerts(tf_zip)
     99 
    100   # apply the key remapping to the contents of the file
    101   for apk, cert in certmap.iteritems():
    102     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    103 
    104   # apply all the -e options, overriding anything in the file
    105   for apk, cert in OPTIONS.extra_apks.iteritems():
    106     if not cert:
    107       cert = "PRESIGNED"
    108     certmap[apk] = OPTIONS.key_map.get(cert, cert)
    109 
    110   return certmap
    111 
    112 
    113 def CheckAllApksSigned(input_tf_zip, apk_key_map):
    114   """Check that all the APKs we want to sign have keys specified, and
    115   error out if they don't."""
    116   unknown_apks = []
    117   for info in input_tf_zip.infolist():
    118     if info.filename.endswith(".apk"):
    119       name = os.path.basename(info.filename)
    120       if name not in apk_key_map:
    121         unknown_apks.append(name)
    122   if unknown_apks:
    123     print "ERROR: no key specified for:\n\n ",
    124     print "\n  ".join(unknown_apks)
    125     print "\nUse '-e <apkname>=' to specify a key (which may be an"
    126     print "empty string to not sign this apk)."
    127     sys.exit(1)
    128 
    129 
    130 def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map):
    131   unsigned = tempfile.NamedTemporaryFile()
    132   unsigned.write(data)
    133   unsigned.flush()
    134 
    135   signed = tempfile.NamedTemporaryFile()
    136 
    137   # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
    138   # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
    139   # didn't change, we don't want its signature to change due to the switch
    140   # from SHA-1 to SHA-256.
    141   # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
    142   # is 18 or higher. For pre-N builds we disable this mechanism by pretending
    143   # that the APK's minSdkVersion is 1.
    144   # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
    145   # determine whether to use SHA-256.
    146   min_api_level = None
    147   if platform_api_level > 23:
    148     # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
    149     # minSdkVersion attribute
    150     min_api_level = None
    151   else:
    152     # Force APK signer to use SHA-1
    153     min_api_level = 1
    154 
    155   common.SignFile(unsigned.name, signed.name, keyname, pw,
    156       min_api_level=min_api_level,
    157       codename_to_api_level_map=codename_to_api_level_map)
    158 
    159   data = signed.read()
    160   unsigned.close()
    161   signed.close()
    162 
    163   return data
    164 
    165 
    166 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
    167                        apk_key_map, key_passwords, platform_api_level,
    168                        codename_to_api_level_map):
    169 
    170   maxsize = max([len(os.path.basename(i.filename))
    171                  for i in input_tf_zip.infolist()
    172                  if i.filename.endswith('.apk')])
    173   rebuild_recovery = False
    174 
    175   tmpdir = tempfile.mkdtemp()
    176   def write_to_temp(fn, attr, data):
    177     fn = os.path.join(tmpdir, fn)
    178     if fn.endswith("/"):
    179       fn = os.path.join(tmpdir, fn)
    180       os.mkdir(fn)
    181     else:
    182       d = os.path.dirname(fn)
    183       if d and not os.path.exists(d):
    184         os.makedirs(d)
    185 
    186       if attr >> 16 == 0xa1ff:
    187         os.symlink(data, fn)
    188       else:
    189         with open(fn, "wb") as f:
    190           f.write(data)
    191 
    192   for info in input_tf_zip.infolist():
    193     if info.filename.startswith("IMAGES/"):
    194       continue
    195 
    196     data = input_tf_zip.read(info.filename)
    197     out_info = copy.copy(info)
    198 
    199     # Replace keys if requested.
    200     if (info.filename == "META/misc_info.txt" and
    201         OPTIONS.replace_verity_private_key):
    202       ReplaceVerityPrivateKey(input_tf_zip, output_tf_zip, misc_info,
    203                               OPTIONS.replace_verity_private_key[1])
    204     elif (info.filename in ("BOOT/RAMDISK/verity_key",
    205                             "BOOT/verity_key") and
    206           OPTIONS.replace_verity_public_key):
    207       new_data = ReplaceVerityPublicKey(output_tf_zip, info.filename,
    208                                         OPTIONS.replace_verity_public_key[1])
    209       write_to_temp(info.filename, info.external_attr, new_data)
    210     # Copy BOOT/, RECOVERY/, META/, ROOT/ to rebuild recovery patch.
    211     elif (info.filename.startswith("BOOT/") or
    212           info.filename.startswith("RECOVERY/") or
    213           info.filename.startswith("META/") or
    214           info.filename.startswith("ROOT/") or
    215           info.filename == "SYSTEM/etc/recovery-resource.dat"):
    216       write_to_temp(info.filename, info.external_attr, data)
    217 
    218     # Sign APKs.
    219     if info.filename.endswith(".apk"):
    220       name = os.path.basename(info.filename)
    221       key = apk_key_map[name]
    222       if key not in common.SPECIAL_CERT_STRINGS:
    223         print "    signing: %-*s (%s)" % (maxsize, name, key)
    224         signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
    225             codename_to_api_level_map)
    226         common.ZipWriteStr(output_tf_zip, out_info, signed_data)
    227       else:
    228         # an APK we're not supposed to sign.
    229         print "NOT signing: %s" % (name,)
    230         common.ZipWriteStr(output_tf_zip, out_info, data)
    231     elif info.filename in ("SYSTEM/build.prop",
    232                            "VENDOR/build.prop",
    233                            "BOOT/RAMDISK/default.prop",
    234                            "RECOVERY/RAMDISK/default.prop"):
    235       print "rewriting %s:" % (info.filename,)
    236       new_data = RewriteProps(data, misc_info)
    237       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    238       if info.filename in ("BOOT/RAMDISK/default.prop",
    239                            "RECOVERY/RAMDISK/default.prop"):
    240         write_to_temp(info.filename, info.external_attr, new_data)
    241     elif info.filename.endswith("mac_permissions.xml"):
    242       print "rewriting %s with new keys." % (info.filename,)
    243       new_data = ReplaceCerts(data)
    244       common.ZipWriteStr(output_tf_zip, out_info, new_data)
    245     elif info.filename in ("SYSTEM/recovery-from-boot.p",
    246                            "SYSTEM/etc/recovery.img",
    247                            "SYSTEM/bin/install-recovery.sh"):
    248       rebuild_recovery = True
    249     elif (OPTIONS.replace_ota_keys and
    250           info.filename in ("RECOVERY/RAMDISK/res/keys",
    251                             "SYSTEM/etc/security/otacerts.zip")):
    252       # don't copy these files if we're regenerating them below
    253       pass
    254     elif (OPTIONS.replace_verity_private_key and
    255           info.filename == "META/misc_info.txt"):
    256       pass
    257     elif (OPTIONS.replace_verity_public_key and
    258           info.filename in ("BOOT/RAMDISK/verity_key",
    259                             "BOOT/verity_key")):
    260       pass
    261     else:
    262       # a non-APK file; copy it verbatim
    263       common.ZipWriteStr(output_tf_zip, out_info, data)
    264 
    265   if OPTIONS.replace_ota_keys:
    266     new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
    267     if new_recovery_keys:
    268       write_to_temp("RECOVERY/RAMDISK/res/keys", 0o755 << 16, new_recovery_keys)
    269 
    270   if rebuild_recovery:
    271     recovery_img = common.GetBootableImage(
    272         "recovery.img", "recovery.img", tmpdir, "RECOVERY", info_dict=misc_info)
    273     boot_img = common.GetBootableImage(
    274         "boot.img", "boot.img", tmpdir, "BOOT", info_dict=misc_info)
    275 
    276     def output_sink(fn, data):
    277       common.ZipWriteStr(output_tf_zip, "SYSTEM/" + fn, data)
    278 
    279     common.MakeRecoveryPatch(tmpdir, output_sink, recovery_img, boot_img,
    280                              info_dict=misc_info)
    281 
    282   shutil.rmtree(tmpdir)
    283 
    284 
    285 def ReplaceCerts(data):
    286   """Given a string of data, replace all occurences of a set
    287   of X509 certs with a newer set of X509 certs and return
    288   the updated data string."""
    289   for old, new in OPTIONS.key_map.iteritems():
    290     try:
    291       if OPTIONS.verbose:
    292         print "    Replacing %s.x509.pem with %s.x509.pem" % (old, new)
    293       f = open(old + ".x509.pem")
    294       old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
    295       f.close()
    296       f = open(new + ".x509.pem")
    297       new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
    298       f.close()
    299       # Only match entire certs.
    300       pattern = "\\b"+old_cert16+"\\b"
    301       (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
    302       if OPTIONS.verbose:
    303         print "    Replaced %d occurence(s) of %s.x509.pem with " \
    304             "%s.x509.pem" % (num, old, new)
    305     except IOError as e:
    306       if e.errno == errno.ENOENT and not OPTIONS.verbose:
    307         continue
    308 
    309       print "    Error accessing %s. %s. Skip replacing %s.x509.pem " \
    310           "with %s.x509.pem." % (e.filename, e.strerror, old, new)
    311 
    312   return data
    313 
    314 
    315 def EditTags(tags):
    316   """Given a string containing comma-separated tags, apply the edits
    317   specified in OPTIONS.tag_changes and return the updated string."""
    318   tags = set(tags.split(","))
    319   for ch in OPTIONS.tag_changes:
    320     if ch[0] == "-":
    321       tags.discard(ch[1:])
    322     elif ch[0] == "+":
    323       tags.add(ch[1:])
    324   return ",".join(sorted(tags))
    325 
    326 
    327 def RewriteProps(data, misc_info):
    328   output = []
    329   for line in data.split("\n"):
    330     line = line.strip()
    331     original_line = line
    332     if line and line[0] != '#' and "=" in line:
    333       key, value = line.split("=", 1)
    334       if (key in ("ro.build.fingerprint", "ro.vendor.build.fingerprint")
    335           and misc_info.get("oem_fingerprint_properties") is None):
    336         pieces = value.split("/")
    337         pieces[-1] = EditTags(pieces[-1])
    338         value = "/".join(pieces)
    339       elif (key in ("ro.build.thumbprint", "ro.vendor.build.thumbprint")
    340             and misc_info.get("oem_fingerprint_properties") is not None):
    341         pieces = value.split("/")
    342         pieces[-1] = EditTags(pieces[-1])
    343         value = "/".join(pieces)
    344       elif key == "ro.bootimage.build.fingerprint":
    345         pieces = value.split("/")
    346         pieces[-1] = EditTags(pieces[-1])
    347         value = "/".join(pieces)
    348       elif key == "ro.build.description":
    349         pieces = value.split(" ")
    350         assert len(pieces) == 5
    351         pieces[-1] = EditTags(pieces[-1])
    352         value = " ".join(pieces)
    353       elif key == "ro.build.tags":
    354         value = EditTags(value)
    355       elif key == "ro.build.display.id":
    356         # change, eg, "JWR66N dev-keys" to "JWR66N"
    357         value = value.split()
    358         if len(value) > 1 and value[-1].endswith("-keys"):
    359           value.pop()
    360         value = " ".join(value)
    361       line = key + "=" + value
    362     if line != original_line:
    363       print "  replace: ", original_line
    364       print "     with: ", line
    365     output.append(line)
    366   return "\n".join(output) + "\n"
    367 
    368 
    369 def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
    370   try:
    371     keylist = input_tf_zip.read("META/otakeys.txt").split()
    372   except KeyError:
    373     raise common.ExternalError("can't read META/otakeys.txt from input")
    374 
    375   extra_recovery_keys = misc_info.get("extra_recovery_keys", None)
    376   if extra_recovery_keys:
    377     extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
    378                            for k in extra_recovery_keys.split()]
    379     if extra_recovery_keys:
    380       print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys)
    381   else:
    382     extra_recovery_keys = []
    383 
    384   mapped_keys = []
    385   for k in keylist:
    386     m = re.match(r"^(.*)\.x509\.pem$", k)
    387     if not m:
    388       raise common.ExternalError(
    389           "can't parse \"%s\" from META/otakeys.txt" % (k,))
    390     k = m.group(1)
    391     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
    392 
    393   if mapped_keys:
    394     print "using:\n   ", "\n   ".join(mapped_keys)
    395     print "for OTA package verification"
    396   else:
    397     devkey = misc_info.get("default_system_dev_certificate",
    398                            "build/target/product/security/testkey")
    399     mapped_keys.append(
    400         OPTIONS.key_map.get(devkey, devkey) + ".x509.pem")
    401     print "META/otakeys.txt has no keys; using", mapped_keys[0]
    402 
    403   # recovery uses a version of the key that has been slightly
    404   # predigested (by DumpPublicKey.java) and put in res/keys.
    405   # extra_recovery_keys are used only in recovery.
    406 
    407   p = common.Run(["java", "-jar",
    408                   os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
    409                  + mapped_keys + extra_recovery_keys,
    410                  stdout=subprocess.PIPE)
    411   new_recovery_keys, _ = p.communicate()
    412   if p.returncode != 0:
    413     raise common.ExternalError("failed to run dumpkeys")
    414   common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys",
    415                      new_recovery_keys)
    416 
    417   # SystemUpdateActivity uses the x509.pem version of the keys, but
    418   # put into a zipfile system/etc/security/otacerts.zip.
    419   # We DO NOT include the extra_recovery_keys (if any) here.
    420 
    421   temp_file = cStringIO.StringIO()
    422   certs_zip = zipfile.ZipFile(temp_file, "w")
    423   for k in mapped_keys:
    424     common.ZipWrite(certs_zip, k)
    425   common.ZipClose(certs_zip)
    426   common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
    427                      temp_file.getvalue())
    428 
    429   return new_recovery_keys
    430 
    431 def ReplaceVerityPublicKey(targetfile_zip, filename, key_path):
    432   print "Replacing verity public key with %s" % key_path
    433   with open(key_path) as f:
    434     data = f.read()
    435   common.ZipWriteStr(targetfile_zip, filename, data)
    436   return data
    437 
    438 def ReplaceVerityPrivateKey(targetfile_input_zip, targetfile_output_zip,
    439                             misc_info, key_path):
    440   print "Replacing verity private key with %s" % key_path
    441   current_key = misc_info["verity_key"]
    442   original_misc_info = targetfile_input_zip.read("META/misc_info.txt")
    443   new_misc_info = original_misc_info.replace(current_key, key_path)
    444   common.ZipWriteStr(targetfile_output_zip, "META/misc_info.txt", new_misc_info)
    445   misc_info["verity_key"] = key_path
    446 
    447 def BuildKeyMap(misc_info, key_mapping_options):
    448   for s, d in key_mapping_options:
    449     if s is None:   # -d option
    450       devkey = misc_info.get("default_system_dev_certificate",
    451                              "build/target/product/security/testkey")
    452       devkeydir = os.path.dirname(devkey)
    453 
    454       OPTIONS.key_map.update({
    455           devkeydir + "/testkey":  d + "/releasekey",
    456           devkeydir + "/devkey":   d + "/releasekey",
    457           devkeydir + "/media":    d + "/media",
    458           devkeydir + "/shared":   d + "/shared",
    459           devkeydir + "/platform": d + "/platform",
    460           })
    461     else:
    462       OPTIONS.key_map[s] = d
    463 
    464 
    465 def GetApiLevelAndCodename(input_tf_zip):
    466   data = input_tf_zip.read("SYSTEM/build.prop")
    467   api_level = None
    468   codename = None
    469   for line in data.split("\n"):
    470     line = line.strip()
    471     original_line = line
    472     if line and line[0] != '#' and "=" in line:
    473       key, value = line.split("=", 1)
    474       key = key.strip()
    475       if key == "ro.build.version.sdk":
    476         api_level = int(value.strip())
    477       elif key == "ro.build.version.codename":
    478         codename = value.strip()
    479 
    480   if api_level is None:
    481     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    482   if codename is None:
    483     raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
    484 
    485   return (api_level, codename)
    486 
    487 
    488 def GetCodenameToApiLevelMap(input_tf_zip):
    489   data = input_tf_zip.read("SYSTEM/build.prop")
    490   api_level = None
    491   codenames = None
    492   for line in data.split("\n"):
    493     line = line.strip()
    494     original_line = line
    495     if line and line[0] != '#' and "=" in line:
    496       key, value = line.split("=", 1)
    497       key = key.strip()
    498       if key == "ro.build.version.sdk":
    499         api_level = int(value.strip())
    500       elif key == "ro.build.version.all_codenames":
    501         codenames = value.strip().split(",")
    502 
    503   if api_level is None:
    504     raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
    505   if codenames is None:
    506     raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
    507 
    508   result = dict()
    509   for codename in codenames:
    510     codename = codename.strip()
    511     if len(codename) > 0:
    512       result[codename] = api_level
    513   return result
    514 
    515 
    516 def main(argv):
    517 
    518   key_mapping_options = []
    519 
    520   def option_handler(o, a):
    521     if o in ("-e", "--extra_apks"):
    522       names, key = a.split("=")
    523       names = names.split(",")
    524       for n in names:
    525         OPTIONS.extra_apks[n] = key
    526     elif o in ("-d", "--default_key_mappings"):
    527       key_mapping_options.append((None, a))
    528     elif o in ("-k", "--key_mapping"):
    529       key_mapping_options.append(a.split("=", 1))
    530     elif o in ("-o", "--replace_ota_keys"):
    531       OPTIONS.replace_ota_keys = True
    532     elif o in ("-t", "--tag_changes"):
    533       new = []
    534       for i in a.split(","):
    535         i = i.strip()
    536         if not i or i[0] not in "-+":
    537           raise ValueError("Bad tag change '%s'" % (i,))
    538         new.append(i[0] + i[1:].strip())
    539       OPTIONS.tag_changes = tuple(new)
    540     elif o == "--replace_verity_public_key":
    541       OPTIONS.replace_verity_public_key = (True, a)
    542     elif o == "--replace_verity_private_key":
    543       OPTIONS.replace_verity_private_key = (True, a)
    544     else:
    545       return False
    546     return True
    547 
    548   args = common.ParseOptions(argv, __doc__,
    549                              extra_opts="e:d:k:ot:",
    550                              extra_long_opts=["extra_apks=",
    551                                               "default_key_mappings=",
    552                                               "key_mapping=",
    553                                               "replace_ota_keys",
    554                                               "tag_changes=",
    555                                               "replace_verity_public_key=",
    556                                               "replace_verity_private_key="],
    557                              extra_option_handler=option_handler)
    558 
    559   if len(args) != 2:
    560     common.Usage(__doc__)
    561     sys.exit(1)
    562 
    563   input_zip = zipfile.ZipFile(args[0], "r")
    564   output_zip = zipfile.ZipFile(args[1], "w")
    565 
    566   misc_info = common.LoadInfoDict(input_zip)
    567 
    568   BuildKeyMap(misc_info, key_mapping_options)
    569 
    570   apk_key_map = GetApkCerts(input_zip)
    571   CheckAllApksSigned(input_zip, apk_key_map)
    572 
    573   key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
    574   platform_api_level, platform_codename = GetApiLevelAndCodename(input_zip)
    575   codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
    576   # Android N will be API Level 24, but isn't yet.
    577   # TODO: Remove this workaround once Android N is officially API Level 24.
    578   if platform_api_level == 23 and platform_codename == "N":
    579     platform_api_level = 24
    580 
    581   ProcessTargetFiles(input_zip, output_zip, misc_info,
    582                      apk_key_map, key_passwords,
    583                      platform_api_level,
    584                      codename_to_api_level_map)
    585 
    586   common.ZipClose(input_zip)
    587   common.ZipClose(output_zip)
    588 
    589   add_img_to_target_files.AddImagesToTargetFiles(args[1])
    590 
    591   print "done."
    592 
    593 
    594 if __name__ == '__main__':
    595   try:
    596     main(sys.argv[1:])
    597   except common.ExternalError, e:
    598     print
    599     print "   ERROR: %s" % (e,)
    600     print
    601     sys.exit(1)
    602