|  | # pylint: disable=g-direct-third-party-import | 
|  | # Copyright 2015 The Bazel Authors. All rights reserved. | 
|  | # | 
|  | # 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. | 
|  |  | 
|  | """Stubifies an AndroidManifest.xml. | 
|  |  | 
|  | Does the following things: | 
|  | - Replaces the Application class in an Android manifest with a stub one | 
|  | - Resolve string and integer resources to their default values | 
|  |  | 
|  | usage: %s [input manifest] [output manifest] [file for old application class] | 
|  |  | 
|  | Writes the old application class into the file designated by the third argument. | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import sys | 
|  | from xml.etree import ElementTree | 
|  |  | 
|  | # Do not edit this line. Copybara replaces it with PY2 migration helper. | 
|  | from absl import app | 
|  | from absl import flags | 
|  |  | 
|  | flags.DEFINE_string("mode", "mobile_install", | 
|  | "mobile_install or instant_run mode") | 
|  | flags.DEFINE_string("input_manifest", None, "The input manifest") | 
|  | flags.DEFINE_string("output_manifest", None, "The output manifest") | 
|  | flags.DEFINE_string( | 
|  | "output_datafile", None, "The output data file that will " | 
|  | "be embedded in the final APK") | 
|  | flags.DEFINE_string( | 
|  | "override_package", None, | 
|  | "The Android package. Override the one specified in the " | 
|  | "input manifest") | 
|  |  | 
|  | FLAGS = flags.FLAGS | 
|  |  | 
|  | ANDROID = "http://schemas.android.com/apk/res/android" | 
|  | READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE" | 
|  | MOBILE_INSTALL_STUB_APPLICATION = ( | 
|  | "com.google.devtools.build.android.incrementaldeployment.StubApplication") | 
|  | INSTANT_RUN_BOOTSTRAP_APPLICATION = ( | 
|  | "com.android.tools.fd.runtime.BootstrapApplication") | 
|  |  | 
|  | # This is global state, but apparently that's the best one can to with | 
|  | # ElementTree :( | 
|  | ElementTree.register_namespace("android", ANDROID) | 
|  |  | 
|  |  | 
|  | class BadManifestException(Exception): | 
|  | pass | 
|  |  | 
|  |  | 
|  | def StubifyMobileInstall(manifest_string): | 
|  | """Does the stubification on an XML string for mobile-install. | 
|  |  | 
|  | Args: | 
|  | manifest_string: the input manifest as a string. | 
|  | Returns: | 
|  | A tuple of (output manifest, old application class, app package) | 
|  | Raises: | 
|  | Exception: if something goes wrong | 
|  | """ | 
|  | manifest, application = _ParseManifest(manifest_string) | 
|  |  | 
|  | old_application = application.get( | 
|  | "{%s}name" % ANDROID, "android.app.Application") | 
|  |  | 
|  | application.set("{%s}name" % ANDROID, MOBILE_INSTALL_STUB_APPLICATION) | 
|  | application.attrib.pop("{%s}hasCode" % ANDROID, None) | 
|  | read_permission = manifest.findall( | 
|  | './uses-permission[@android:name="%s"]' % READ_EXTERNAL_STORAGE, | 
|  | namespaces={"android": ANDROID}) | 
|  |  | 
|  | if not read_permission: | 
|  | read_permission = ElementTree.Element("uses-permission") | 
|  | read_permission.set("{%s}name" % ANDROID, READ_EXTERNAL_STORAGE) | 
|  | manifest.insert(0, read_permission) | 
|  |  | 
|  | new_manifest = ElementTree.tostring(manifest) | 
|  | app_package = manifest.get("package") | 
|  | if not app_package: | 
|  | raise BadManifestException("manifest tag does not have a package specified") | 
|  | return (new_manifest, old_application, app_package) | 
|  |  | 
|  |  | 
|  | def StubifyInstantRun(manifest_string): | 
|  | """Stubifies the manifest for Instant Run. | 
|  |  | 
|  | Args: | 
|  | manifest_string: the input manifest as a string. | 
|  | Returns: | 
|  | The new manifest as a string. | 
|  | Raises: | 
|  | Exception: if something goes wrong | 
|  | """ | 
|  | manifest, application = _ParseManifest(manifest_string) | 
|  | old_application = application.get("{%s}name" % ANDROID) | 
|  | if old_application: | 
|  | application.set("name", old_application) | 
|  | application.set("{%s}name" % ANDROID, INSTANT_RUN_BOOTSTRAP_APPLICATION) | 
|  | return ElementTree.tostring(manifest) | 
|  |  | 
|  |  | 
|  | def _ParseManifest(manifest_string): | 
|  | """Parses the given manifest xml. | 
|  |  | 
|  | Args: | 
|  | manifest_string: the manifest as a string. | 
|  | Returns: | 
|  | a tuple of the manifest ElementTree and the application tag. | 
|  | Raises: | 
|  | BadManifestException: if the manifest is bad. | 
|  | """ | 
|  | manifest = ElementTree.fromstring(manifest_string) | 
|  | if manifest.tag != "manifest": | 
|  | raise BadManifestException("invalid input manifest") | 
|  |  | 
|  | app_list = manifest.findall("application") | 
|  | if len(app_list) == 1: | 
|  | # <application> element is present | 
|  | application = app_list[0] | 
|  | elif len(app_list) == 0:  # pylint: disable=g-explicit-length-test | 
|  | # <application> element is not present | 
|  | application = ElementTree.Element("application") | 
|  | manifest.insert(0, application) | 
|  | else: | 
|  | raise BadManifestException("multiple <application> elements present") | 
|  | return (manifest, application) | 
|  |  | 
|  |  | 
|  | def main(unused_argv): | 
|  | if FLAGS.mode == "mobile_install": | 
|  | with open(FLAGS.input_manifest, "rb") as input_manifest: | 
|  | new_manifest, old_application, app_package = ( | 
|  | StubifyMobileInstall(input_manifest.read())) | 
|  |  | 
|  | if FLAGS.override_package: | 
|  | app_package = FLAGS.override_package | 
|  |  | 
|  | with open(FLAGS.output_manifest, "wb") as output_xml: | 
|  | output_xml.write(new_manifest) | 
|  |  | 
|  | with open(FLAGS.output_datafile, "wb") as output_file: | 
|  | output_file.write("\n".join([old_application, app_package]).encode()) | 
|  |  | 
|  | elif FLAGS.mode == "instant_run": | 
|  | with open(FLAGS.input_manifest, "rb") as input_manifest: | 
|  | new_manifest = StubifyInstantRun(input_manifest.read()) | 
|  |  | 
|  | with open(FLAGS.output_manifest, "wb") as output_xml: | 
|  | output_xml.write(new_manifest) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | try: | 
|  | app.run(main) | 
|  | except BadManifestException as e: | 
|  | print(e) | 
|  | sys.exit(1) |