Merge "Make PRODUCT_CHARACTERISTICS a single-valued variable"
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index d14c94f..fb4ca76 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -19,11 +19,14 @@
 import re
 import shlex
 import sys
+import zipfile
 
 import common
 
 logger = logging.getLogger(__name__)
 
+OPTIONS = common.OPTIONS
+
 
 class ApexInfoError(Exception):
   """An Exception raised during Apex Information command."""
@@ -145,3 +148,72 @@
           'Failed to find {} prop in {}'.format(key, payload_path))
 
   return payload_info
+
+
+def SignApex(apex_data, payload_key, container_key, container_pw,
+             codename_to_api_level_map, signing_args=None):
+  """Signs the current APEX with the given payload/container keys.
+
+  Args:
+    apex_data: Raw APEX data.
+    payload_key: The path to payload signing key (w/ extension).
+    container_key: The path to container signing key (w/o extension).
+    container_pw: The matching password of the container_key, or None.
+    codename_to_api_level_map: A dict that maps from codename to API level.
+    signing_args: Additional args to be passed to the payload signer.
+
+  Returns:
+    The path to the signed APEX file.
+  """
+  apex_file = common.MakeTempFile(prefix='apex-', suffix='.apex')
+  with open(apex_file, 'wb') as apex_fp:
+    apex_fp.write(apex_data)
+
+  APEX_PAYLOAD_IMAGE = 'apex_payload.img'
+  APEX_PUBKEY = 'apex_pubkey'
+
+  # 1a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given
+  # payload_key.
+  payload_dir = common.MakeTempDir(prefix='apex-payload-')
+  with zipfile.ZipFile(apex_file) as apex_fd:
+    payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir)
+
+  payload_info = ParseApexPayloadInfo(payload_file)
+  SignApexPayload(
+      payload_file,
+      payload_key,
+      payload_info['apex.key'],
+      payload_info['Algorithm'],
+      payload_info['Salt'],
+      signing_args)
+
+  # 1b. Update the embedded payload public key.
+  payload_public_key = common.ExtractAvbPublicKey(payload_key)
+
+  common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE)
+  common.ZipDelete(apex_file, APEX_PUBKEY)
+  apex_zip = zipfile.ZipFile(apex_file, 'a')
+  common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE)
+  common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY)
+  common.ZipClose(apex_zip)
+
+  # 2. Align the files at page boundary (same as in apexer).
+  aligned_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
+  common.RunAndCheckOutput(['zipalign', '-f', '4096', apex_file, aligned_apex])
+
+  # 3. Sign the APEX container with container_key.
+  signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
+
+  # Specify the 4K alignment when calling SignApk.
+  extra_signapk_args = OPTIONS.extra_signapk_args[:]
+  extra_signapk_args.extend(['-a', '4096'])
+
+  common.SignFile(
+      aligned_apex,
+      signed_apex,
+      container_key,
+      container_pw,
+      codename_to_api_level_map=codename_to_api_level_map,
+      extra_signapk_args=extra_signapk_args)
+
+  return signed_apex
diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py
new file mode 100755
index 0000000..1778615
--- /dev/null
+++ b/tools/releasetools/sign_apex.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Signs a standalone APEX file.
+
+Usage:  sign_apex [flags] input_apex_file output_apex_file
+
+  --container_key <key>
+      Mandatory flag that specifies the container signing key.
+
+  --payload_key <key>
+      Mandatory flag that specifies the payload signing key.
+
+  --payload_extra_args <args>
+      Optional flag that specifies any extra args to be passed to payload signer
+      (e.g. --payload_extra_args="--signing_helper_with_files /path/to/helper").
+"""
+
+import logging
+import shutil
+import sys
+
+import apex_utils
+import common
+
+logger = logging.getLogger(__name__)
+
+
+def main(argv):
+
+  options = {}
+
+  def option_handler(o, a):
+    if o == '--container_key':
+      # Strip the suffix if any, as common.SignFile expects no suffix.
+      DEFAULT_CONTAINER_KEY_SUFFIX = '.x509.pem'
+      if a.endswith(DEFAULT_CONTAINER_KEY_SUFFIX):
+        a = a[:-len(DEFAULT_CONTAINER_KEY_SUFFIX)]
+      options['container_key'] = a
+    elif o == '--payload_key':
+      options['payload_key'] = a
+    elif o == '--payload_extra_args':
+      options['payload_extra_args'] = a
+    else:
+      return False
+    return True
+
+  args = common.ParseOptions(
+      argv, __doc__,
+      extra_opts='',
+      extra_long_opts=[
+          'container_key=',
+          'payload_extra_args=',
+          'payload_key=',
+      ],
+      extra_option_handler=option_handler)
+
+  if (len(args) != 2 or 'container_key' not in options or
+      'payload_key' not in options):
+    common.Usage(__doc__)
+    sys.exit(1)
+
+  common.InitLogging()
+
+  input_zip = args[0]
+  output_zip = args[1]
+  with open(input_zip) as input_fp:
+    apex_data = input_fp.read()
+
+  signed_apex = apex_utils.SignApex(
+      apex_data,
+      payload_key=options['payload_key'],
+      container_key=options['container_key'],
+      container_pw=None,
+      codename_to_api_level_map=None,
+      signing_args=options.get('payload_extra_args'))
+
+  shutil.copyfile(signed_apex, output_zip)
+  logger.info("done.")
+
+
+if __name__ == '__main__':
+  try:
+    main(sys.argv[1:])
+  except common.ExternalError:
+    logger.exception("\n   ERROR:\n")
+    sys.exit(1)
+  finally:
+    common.Cleanup()
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 24ee91b..7de0978 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -403,77 +403,6 @@
   return data
 
 
-def SignApex(apex_data, payload_key, container_key, container_pw,
-             codename_to_api_level_map, signing_args=None):
-  """Signs the current APEX with the given payload/container keys.
-
-  Args:
-    apex_data: Raw APEX data.
-    payload_key: The path to payload signing key (w/ extension).
-    container_key: The path to container signing key (w/o extension).
-    container_pw: The matching password of the container_key, or None.
-    codename_to_api_level_map: A dict that maps from codename to API level.
-    signing_args: Additional args to be passed to the payload signer.
-
-  Returns:
-    The path to the signed APEX file.
-  """
-  apex_file = common.MakeTempFile(prefix='apex-', suffix='.apex')
-  with open(apex_file, 'wb') as apex_fp:
-    apex_fp.write(apex_data)
-
-  APEX_PAYLOAD_IMAGE = 'apex_payload.img'
-  APEX_PUBKEY = 'apex_pubkey'
-
-  # 1a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given
-  # payload_key.
-  payload_dir = common.MakeTempDir(prefix='apex-payload-')
-  with zipfile.ZipFile(apex_file) as apex_fd:
-    payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir)
-
-  payload_info = apex_utils.ParseApexPayloadInfo(payload_file)
-  apex_utils.SignApexPayload(
-      payload_file,
-      payload_key,
-      payload_info['apex.key'],
-      payload_info['Algorithm'],
-      payload_info['Salt'],
-      signing_args)
-
-  # 1b. Update the embedded payload public key.
-  payload_public_key = common.ExtractAvbPublicKey(payload_key)
-
-  common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE)
-  common.ZipDelete(apex_file, APEX_PUBKEY)
-  apex_zip = zipfile.ZipFile(apex_file, 'a')
-  common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE)
-  common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY)
-  common.ZipClose(apex_zip)
-
-  # 2. Align the files at page boundary (same as in apexer).
-  aligned_apex = common.MakeTempFile(
-      prefix='apex-container-', suffix='.apex')
-  common.RunAndCheckOutput(
-      ['zipalign', '-f', '4096', apex_file, aligned_apex])
-
-  # 3. Sign the APEX container with container_key.
-  signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
-
-  # Specify the 4K alignment when calling SignApk.
-  extra_signapk_args = OPTIONS.extra_signapk_args[:]
-  extra_signapk_args.extend(['-a', '4096'])
-
-  common.SignFile(
-      aligned_apex,
-      signed_apex,
-      container_key,
-      container_pw,
-      codename_to_api_level_map=codename_to_api_level_map,
-      extra_signapk_args=extra_signapk_args)
-
-  return signed_apex
-
-
 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
                        apk_keys, apex_keys, key_passwords,
                        platform_api_level, codename_to_api_level_map,
@@ -538,7 +467,7 @@
         print("           : %-*s payload   (%s)" % (
             maxsize, name, payload_key))
 
-        signed_apex = SignApex(
+        signed_apex = apex_utils.SignApex(
             data,
             payload_key,
             container_key,