Reland: Android: Use a .json file to record incremental install details

This reverts commit 6f34424ff93b31f73b481b7e04ebba0e675983a3.

Reason for reland: Fixes test run scripts

With the recent addition of apk wrapper scripts, the
bin/install_foo_incremental scripts are now just confusing.
This changes them to instead be .json files.

Change-Id: I32b29b7a464697004b5d7eda57cc04e61a81d763
No-Try: true
Bug: 749239
Reviewed-on: https://chromium-review.googlesource.com/612190
Reviewed-by: John Budorick <[email protected]>
Reviewed-by: Andrew Grieve <[email protected]>
Commit-Queue: Andrew Grieve <[email protected]>
Cr-Commit-Position: refs/heads/master@{#493844}
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index 0e37159d..2f695f4 100644
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import argparse
-import imp
+import json
 import logging
 import os
 import pipes
@@ -18,44 +18,26 @@
 from devil.android.sdk import adb_wrapper
 from devil.utils import run_tests_helper
 
+from incremental_install import installer
 from pylib import constants
 
 
-def _InstallApk(install_incremental, inc_install_script, devices_obj,
-                apk_to_install):
-  if install_incremental:
-    helper = apk_helper.ApkHelper(apk_to_install)
-    try:
-      install_wrapper = imp.load_source('install_wrapper', inc_install_script)
-    except IOError:
-      raise Exception('Incremental install script not found: %s\n' %
-                      inc_install_script)
-    params = install_wrapper.GetInstallParameters()
-
-    def install_incremental_apk(device):
-      from incremental_install import installer
-      installer.Install(device, helper, split_globs=params['splits'],
-                        native_libs=params['native_libs'],
-                        dex_files=params['dex_files'], permissions=None)
-    devices_obj.pMap(install_incremental_apk)
-  else:
-    # Install the regular apk on devices.
-    def install(device):
-      device.Install(apk_to_install)
-    devices_obj.pMap(install)
+def _InstallApk(apk, install_dict, devices_obj):
+  def install(device):
+    if install_dict:
+      installer.Install(device, install_dict, apk=apk)
+    else:
+      device.Install(apk)
+  devices_obj.pMap(install)
 
 
-def _UninstallApk(install_incremental, devices_obj, apk_package):
-  if install_incremental:
-    def uninstall_incremental_apk(device):
-      from incremental_install import installer
+def _UninstallApk(install_dict, devices_obj, apk_package):
+  def uninstall(device):
+    if install_dict:
       installer.Uninstall(device, apk_package)
-    devices_obj.pMap(uninstall_incremental_apk)
-  else:
-    # Uninstall the regular apk on devices.
-    def uninstall(device):
+    else:
       device.Uninstall(apk_package)
-    devices_obj.pMap(uninstall)
+  devices_obj.pMap(uninstall)
 
 
 def _LaunchUrl(devices_obj, input_args, device_args_file, url, apk_package):
@@ -160,8 +142,60 @@
   return os.path.join(constants.GetOutDirectory(), file_name)
 
 
-def Run(output_directory, apk_path, inc_apk_path, inc_install_script,
-         command_line_flags_file):
+def _SelectApk(apk_path, incremental_install_json_path, parser, args):
+  if apk_path and not os.path.exists(apk_path):
+    apk_path = None
+  if (incremental_install_json_path and
+      not os.path.exists(incremental_install_json_path)):
+    incremental_install_json_path = None
+
+  if args.incremental and args.non_incremental:
+    parser.error('--incremental and --non-incremental cannot both be used.')
+  elif args.non_incremental:
+    if not apk_path:
+      parser.error('Apk has not been built.')
+    incremental_install_json_path = None
+  elif args.incremental:
+    if not incremental_install_json_path:
+      parser.error('Incremental apk has not been built.')
+    apk_path = None
+
+  if args.command in ('install', 'run'):
+    if apk_path and incremental_install_json_path:
+      parser.error('Both incremental and non-incremental apks exist, please '
+                   'use --incremental or --non-incremental to select one.')
+    elif apk_path:
+      logging.info('Using the non-incremental apk.')
+    elif incremental_install_json_path:
+      logging.info('Using the incremental apk.')
+    else:
+      parser.error('Neither incremental nor non-incremental apk is built.')
+  return apk_path, incremental_install_json_path
+
+
+def _LoadDeviceCaches(devices):
+  for d in devices:
+    cache_path = _DeviceCachePath(d)
+    if os.path.exists(cache_path):
+      logging.info('Using device cache: %s', cache_path)
+      with open(cache_path) as f:
+        d.LoadCacheData(f.read())
+      # Delete the cached file so that any exceptions cause it to be cleared.
+      os.unlink(cache_path)
+    else:
+      logging.info('No cache present for device: %s', d)
+
+
+def _SaveDeviceCaches(devices):
+  for d in devices:
+    cache_path = _DeviceCachePath(d)
+    with open(cache_path, 'w') as f:
+      f.write(d.DumpCacheData())
+      logging.info('Wrote device cache: %s', cache_path)
+
+
+def Run(output_directory, apk_path, incremental_install_json_path,
+        command_line_flags_file):
   constants.SetOutputDirectory(output_directory)
 
   parser = argparse.ArgumentParser()
@@ -229,79 +263,33 @@
   if len(devices) > 1 and not args.all:
     raise Exception(_GenerateMissingAllFlagMessage(devices, devices_obj))
 
-  if args.incremental and args.non_incremental:
-    raise Exception('--incremental and --non-incremental cannot be set at the '
-                    'same time.')
-  install_incremental = False
-  active_apk = None
-  apk_package = None
   apk_name = os.path.basename(apk_path)
-  if apk_path and not os.path.exists(apk_path):
-    apk_path = None
+  apk_path, incremental_install_json_path = _SelectApk(
+      apk_path, incremental_install_json_path, parser, args)
+  install_dict = None
 
-  if args.non_incremental:
-    if apk_path:
-      active_apk = apk_path
-      logging.info('Use the non-incremental apk.')
-    else:
-      raise Exception("No regular apk is available.")
+  if incremental_install_json_path:
+    with open(incremental_install_json_path) as f:
+      install_dict = json.load(f)
+    apk = apk_helper.ToHelper(
+        os.path.join(output_directory, install_dict['apk_path']))
+  else:
+    apk = apk_helper.ToHelper(apk_path)
 
-  if inc_apk_path and not os.path.exists(inc_apk_path):
-    inc_apk_path = None
-
-  if args.incremental:
-    if inc_apk_path:
-      active_apk = inc_apk_path
-      install_incremental = True
-      logging.info('Use the incremental apk.')
-    else:
-      raise Exception("No incremental apk is available.")
-
-  if not args.incremental and not args.non_incremental and command in {
-      'install', 'run'}:
-    if apk_path and inc_apk_path:
-      raise Exception('Both incremental and non-incremental apks exist, please '
-                      'use --incremental or --non-incremental to select one.')
-    if not apk_path and not inc_apk_path:
-      raise Exception('Neither incremental nor non-incremental apk is '
-                      'available.')
-    if apk_path:
-      active_apk = apk_path
-      logging.info('Use the non-incremental apk.')
-    else:
-      active_apk = inc_apk_path
-      install_incremental = True
-      logging.info('Use the incremental apk.')
-
-  if apk_path is not None:
-    apk_package = apk_helper.GetPackageName(apk_path)
-  elif inc_apk_path is not None:
-    apk_package = apk_helper.GetPackageName(inc_apk_path)
+  apk_package = apk.GetPackageName()
 
   if use_cache:
-    for d in devices:
-      cache_path = _DeviceCachePath(d)
-      if os.path.exists(cache_path):
-        logging.info('Using device cache: %s', cache_path)
-        with open(cache_path) as f:
-          d.LoadCacheData(f.read())
-        # Delete the cached file so that any exceptions cause it to be cleared.
-        os.unlink(cache_path)
-      else:
-        logging.info('No cache present for device: %s', d)
+    _LoadDeviceCaches(devices)
 
   if command == 'install':
-    _InstallApk(install_incremental, inc_install_script, devices_obj,
-                active_apk)
+    _InstallApk(apk, install_dict, devices_obj)
   elif command == 'uninstall':
-    _UninstallApk(install_incremental, devices_obj, apk_package)
+    _UninstallApk(install_dict, devices_obj, apk_package)
   elif command == 'launch':
     _LaunchUrl(devices_obj, args.args, command_line_flags_file,
                args.url, apk_package)
   elif command == 'run':
-    _InstallApk(install_incremental, inc_install_script, devices_obj,
-                active_apk)
-    devices_obj.pFinish(None)
+    _InstallApk(apk, install_dict, devices_obj)
     _LaunchUrl(devices_obj, args.args, command_line_flags_file,
                args.url, apk_package)
   elif command == 'stop':
@@ -336,13 +324,6 @@
     flags = [adb_path, '-s', devices[0].adb.GetDeviceSerial(), 'logcat']
     os.execv(adb_path, flags)
 
-  # Wait for all threads to finish.
-  devices_obj.pFinish(None)
-
   # Save back to the cache.
   if use_cache:
-    for d in devices:
-      cache_path = _DeviceCachePath(d)
-      with open(cache_path, 'w') as f:
-        f.write(d.DumpCacheData())
-        logging.info('Wrote device cache: %s', cache_path)
+    _SaveDeviceCaches(devices)
diff --git a/build/android/gyp/create_apk_operations_script.py b/build/android/gyp/create_apk_operations_script.py
index 4d426f42..dddeb7a 100755
--- a/build/android/gyp/create_apk_operations_script.py
+++ b/build/android/gyp/create_apk_operations_script.py
@@ -24,10 +24,9 @@
       script_directory, p))
   sys.path.append(resolve(${APK_OPERATIONS_DIR}))
   import apk_operations
-  apk_operations.Run(output_directory=resolve(${OUTPUT_DIR}),
-                     apk_path=resolve(${APK_PATH}),
-                     inc_apk_path=resolve(${INC_APK_PATH}),
-                     inc_install_script=resolve(${INC_INSTALL_SCRIPT}),
+  apk_operations.Run(resolve(${OUTPUT_DIR}),
+                     resolve(${APK_PATH}),
+                     resolve(${INC_JSON_PATH}),
                      command_line_flags_file=${FLAGS_FILE})
 
 
@@ -41,8 +40,7 @@
   parser.add_argument('--script-output-path',
                       help='Output path for executable script.')
   parser.add_argument('--apk-path')
-  parser.add_argument('--incremental-apk-path')
-  parser.add_argument('--incremental-install-script')
+  parser.add_argument('--incremental-install-json-path')
   parser.add_argument('--command-line-flags-file')
   args = parser.parse_args(args)
 
@@ -59,8 +57,7 @@
         'APK_OPERATIONS_DIR': repr(apk_operations_dir),
         'OUTPUT_DIR': repr(relativize('.')),
         'APK_PATH': repr(relativize(args.apk_path)),
-        'INC_APK_PATH': repr(relativize(args.incremental_apk_path)),
-        'INC_INSTALL_SCRIPT': repr(relativize(args.incremental_install_script)),
+        'INC_JSON_PATH': repr(relativize(args.incremental_install_json_path)),
         'FLAGS_FILE': repr(args.command_line_flags_file),
     }
     script.write(SCRIPT_TEMPLATE.substitute(script_dict))
diff --git a/build/android/gyp/create_test_runner_script.py b/build/android/gyp/create_test_runner_script.py
index f6a2c3d..a67da882 100755
--- a/build/android/gyp/create_test_runner_script.py
+++ b/build/android/gyp/create_test_runner_script.py
@@ -70,14 +70,14 @@
   group.add_argument('--additional-apk-incremental', action='append',
                      dest='additional_apks_incremental', default=[])
   group.add_argument('--apk-under-test')
-  group.add_argument('--apk-under-test-incremental-install-script')
+  group.add_argument('--apk-under-test-incremental-install-json')
   group.add_argument('--executable-dist-dir')
   group.add_argument('--isolate-file-path')
   group.add_argument('--output-directory')
   group.add_argument('--runtime-deps-path')
   group.add_argument('--test-apk')
   group.add_argument('--test-jar')
-  group.add_argument('--test-apk-incremental-install-script')
+  group.add_argument('--test-apk-incremental-install-json')
   group.add_argument('--coverage-dir')
   group.add_argument('--android-manifest-path')
   group.add_argument('--resource-zips')
@@ -112,11 +112,11 @@
   if args.apk_under_test:
     test_runner_path_args.append(
         ('--apk-under-test', RelativizePathToScript(args.apk_under_test)))
-  if args.apk_under_test_incremental_install_script:
+  if args.apk_under_test_incremental_install_json:
     test_runner_path_args.append(
-        ('--apk-under-test-incremental-install-script',
+        ('--apk-under-test-incremental-install-json',
          RelativizePathToScript(
-             args.apk_under_test_incremental_install_script)))
+             args.apk_under_test_incremental_install_json)))
   if args.executable_dist_dir:
     test_runner_path_args.append(
         ('--executable-dist-dir',
@@ -136,10 +136,10 @@
   if args.test_jar:
     test_runner_path_args.append(
         ('--test-jar', RelativizePathToScript(args.test_jar)))
-  if args.test_apk_incremental_install_script:
+  if args.test_apk_incremental_install_json:
     test_runner_path_args.append(
-        ('--test-apk-incremental-install-script',
-         RelativizePathToScript(args.test_apk_incremental_install_script)))
+        ('--test-apk-incremental-install-json',
+         RelativizePathToScript(args.test_apk_incremental_install_json)))
   if args.coverage_dir:
     test_runner_path_args.append(
         ('--coverage-dir', RelativizePathToScript(args.coverage_dir)))
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index 63a8ee8..f3617ae 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -312,9 +312,9 @@
   parser.add_option('--apk-path', help='Path to the target\'s apk output.')
   parser.add_option('--incremental-apk-path',
                     help="Path to the target's incremental apk output.")
-  parser.add_option('--incremental-install-script-path',
+  parser.add_option('--incremental-install-json-path',
                     help="Path to the target's generated incremental install "
-                    "script.")
+                    "json.")
 
   parser.add_option('--tested-apk-config',
       help='Path to the build config of the tested apk (for an instrumentation '
@@ -472,8 +472,8 @@
     if options.type == 'android_apk':
       deps_info['apk_path'] = options.apk_path
       deps_info['incremental_apk_path'] = options.incremental_apk_path
-      deps_info['incremental_install_script_path'] = (
-          options.incremental_install_script_path)
+      deps_info['incremental_install_json_path'] = (
+          options.incremental_install_json_path)
       deps_info['enable_relocation_packing'] = options.enable_relocation_packing
 
   if options.type in ('java_binary', 'java_library', 'android_apk', 'dist_jar'):
diff --git a/build/android/incremental_install/create_install_script.py b/build/android/incremental_install/create_install_script.py
deleted file mode 100755
index 09004da..0000000
--- a/build/android/incremental_install/create_install_script.py
+++ /dev/null
@@ -1,158 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Creates a script to run an "_incremental" .apk."""
-
-import argparse
-import os
-import pprint
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
-sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
-
-from pylib.constants import host_paths
-from util import build_utils
-
-
-SCRIPT_TEMPLATE = """\
-#!/usr/bin/env python
-#
-# This file was generated by:
-#     //build/android/incremental_install/create_install_script.py
-
-import os
-import subprocess
-import sys
-
-
-def _ResolvePath(path):
-  script_directory = os.path.dirname(__file__)
-  return os.path.abspath(os.path.join(script_directory, path))
-
-
-# Exported to allow test runner to be able to install incremental apks.
-def GetInstallParameters():
-  apk_path = {apk_path}
-  dex_files = {dex_files}
-  dont_even_try = {dont_even_try}
-  native_libs = {native_libs}
-  show_proguard_warning = {show_proguard_warning}
-  splits = {splits}
-
-  return dict(apk_path=_ResolvePath(apk_path),
-              dex_files=[_ResolvePath(p) for p in dex_files],
-              dont_even_try=dont_even_try,
-              native_libs=[_ResolvePath(p) for p in native_libs],
-              show_proguard_warning=show_proguard_warning,
-              splits=[_ResolvePath(p) for p in splits])
-
-
-def main():
-  output_directory = {output_directory}
-  cmd_path = {cmd_path}
-  params = GetInstallParameters()
-  cmd_args = [
-      _ResolvePath(cmd_path),
-      '--output-directory', _ResolvePath(output_directory),
-  ]
-  for native_lib in params['native_libs']:
-    cmd_args.extend(('--native_lib', native_lib))
-  for dex_path in params['dex_files']:
-    cmd_args.extend(('--dex-file', dex_path))
-  for split in params['splits']:
-    cmd_args.extend(('--split', split))
-  cmd_args.append(params['apk_path'])
-  if params['dont_even_try']:
-    cmd_args.extend(('--dont-even-try', params['dont_even_try']))
-  if params['show_proguard_warning']:
-    cmd_args.append('--show-proguard-warning')
-  return subprocess.call(cmd_args + sys.argv[1:])
-
-if __name__ == '__main__':
-  sys.exit(main())
-"""
-
-
-def _ParseArgs(args):
-  args = build_utils.ExpandFileArgs(args)
-  parser = argparse.ArgumentParser()
-  build_utils.AddDepfileOption(parser)
-  parser.add_argument('--script-output-path',
-                      help='Output path for executable script.',
-                      required=True)
-  parser.add_argument('--output-directory',
-                      help='Path to the root build directory.',
-                      default='.')
-  parser.add_argument('--apk-path',
-                      help='Path to the .apk to install.',
-                      required=True)
-  parser.add_argument('--split',
-                      action='append',
-                      dest='splits',
-                      default=[],
-                      help='A glob matching the apk splits. '
-                           'Can be specified multiple times.')
-  parser.add_argument('--native-libs',
-                      action='append',
-                      default=[],
-                      help='GYP-list of paths to native libraries. Can be '
-                      'repeated.')
-  parser.add_argument('--dex-file',
-                      action='append',
-                      default=[],
-                      dest='dex_files',
-                      help='List of dex files to include.')
-  parser.add_argument('--dex-file-list',
-                      help='GYP-list of dex files.')
-  parser.add_argument('--show-proguard-warning',
-                      action='store_true',
-                      default=False,
-                      help='Print a warning about proguard being disabled')
-  parser.add_argument('--dont-even-try',
-                      help='Prints this message and exits.')
-
-  options = parser.parse_args(args)
-  options.dex_files += build_utils.ParseGnList(options.dex_file_list)
-  all_libs = []
-  for gyp_list in options.native_libs:
-    all_libs.extend(build_utils.ParseGnList(gyp_list))
-  options.native_libs = all_libs
-  return options
-
-
-def main(args):
-  options = _ParseArgs(args)
-
-  def relativize(path):
-    script_dir = os.path.dirname(options.script_output_path)
-    return path and os.path.relpath(path, script_dir)
-
-  installer_path = os.path.join(host_paths.DIR_SOURCE_ROOT, 'build', 'android',
-                                'incremental_install', 'installer.py')
-  pformat = pprint.pformat
-  template_args = {
-      'cmd_path': pformat(relativize(installer_path)),
-      'apk_path': pformat(relativize(options.apk_path)),
-      'output_directory': pformat(relativize(options.output_directory)),
-      'native_libs': pformat([relativize(p) for p in options.native_libs]),
-      'dex_files': pformat([relativize(p) for p in options.dex_files]),
-      'dont_even_try': pformat(options.dont_even_try),
-      'show_proguard_warning': pformat(options.show_proguard_warning),
-      'splits': pformat([relativize(p) for p in options.splits]),
-  }
-
-  with open(options.script_output_path, 'w') as script:
-    script.write(SCRIPT_TEMPLATE.format(**template_args))
-
-  os.chmod(options.script_output_path, 0750)
-
-  if options.depfile:
-    build_utils.WriteDepfile(options.depfile, options.script_output_path)
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
diff --git a/build/android/incremental_install/installer.py b/build/android/incremental_install/installer.py
index 54abf76..92f86af 100755
--- a/build/android/incremental_install/installer.py
+++ b/build/android/incremental_install/installer.py
@@ -8,6 +8,7 @@
 
 import argparse
 import glob
+import json
 import logging
 import os
 import posixpath
@@ -83,31 +84,43 @@
   logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
 
 
-def Install(device, apk, split_globs=None, native_libs=None, dex_files=None,
-            enable_device_cache=False, use_concurrency=True,
-            show_proguard_warning=False, permissions=(),
-            allow_downgrade=True):
+def Install(device, install_json, apk=None, enable_device_cache=False,
+            use_concurrency=True, permissions=()):
   """Installs the given incremental apk and all required supporting files.
 
   Args:
-    device: A DeviceUtils instance.
-    apk: The path to the apk, or an ApkHelper instance.
-    split_globs: Glob patterns for any required apk splits (optional).
-    native_libs: List of app's native libraries (optional).
-    dex_files: List of .dex.jar files that comprise the app's Dalvik code.
+    device: A DeviceUtils instance (to install to).
+    install_json: Path to .json file or already parsed .json object.
+    apk: An existing ApkHelper instance for the apk (optional).
     enable_device_cache: Whether to enable on-device caching of checksums.
     use_concurrency: Whether to speed things up using multiple threads.
-    show_proguard_warning: Whether to print a warning about Proguard not being
-        enabled after installing.
     permissions: A list of the permissions to grant, or None to grant all
                  non-blacklisted permissions in the manifest.
   """
+  if isinstance(install_json, basestring):
+    with open(install_json) as f:
+      install_dict = json.load(f)
+  else:
+    install_dict = install_json
+
+  if install_dict.get('dont_even_try'):
+    raise Exception(install_dict['dont_even_try'])
+
   main_timer = time_profile.TimeProfile()
   install_timer = time_profile.TimeProfile()
   push_native_timer = time_profile.TimeProfile()
   push_dex_timer = time_profile.TimeProfile()
 
-  apk = apk_helper.ToHelper(apk)
+  def fix_path(p):
+    return os.path.normpath(os.path.join(constants.GetOutDirectory(), p))
+
+  if not apk:
+    apk = apk_helper.ToHelper(fix_path(install_dict['apk_path']))
+  split_globs = [fix_path(p) for p in install_dict['split_globs']]
+  native_libs = [fix_path(p) for p in install_dict['native_libs']]
+  dex_files = [fix_path(p) for p in install_dict['dex_files']]
+  show_proguard_warning = install_dict.get('show_proguard_warning')
+
   apk_package = apk.GetPackageName()
   device_incremental_dir = _GetDeviceIncrementalDir(apk_package)
 
@@ -119,11 +132,9 @@
       for split_glob in split_globs:
         splits.extend((f for f in glob.glob(split_glob)))
       device.InstallSplitApk(apk, splits, reinstall=True,
-                             allow_cached_props=True, permissions=permissions,
-                             allow_downgrade=allow_downgrade)
+                             allow_cached_props=True, permissions=permissions)
     else:
-      device.Install(apk, reinstall=True, permissions=permissions,
-                     allow_downgrade=allow_downgrade)
+      device.Install(apk, reinstall=True, permissions=permissions)
     install_timer.Stop(log=False)
 
   # Push .so and .dex files to the device (if they have changed).
@@ -227,23 +238,8 @@
 
 def main():
   parser = argparse.ArgumentParser()
-  parser.add_argument('apk_path',
-                      help='The path to the APK to install.')
-  parser.add_argument('--split',
-                      action='append',
-                      dest='splits',
-                      help='A glob matching the apk splits. '
-                           'Can be specified multiple times.')
-  parser.add_argument('--native_lib',
-                      dest='native_libs',
-                      help='Path to native library (repeatable)',
-                      action='append',
-                      default=[])
-  parser.add_argument('--dex-file',
-                      dest='dex_files',
-                      help='Path to dex files (repeatable)',
-                      action='append',
-                      default=[])
+  parser.add_argument('json_path',
+                      help='The path to the generated incremental apk .json.')
   parser.add_argument('-d', '--device', dest='device',
                       help='Target device for apk to install on.')
   parser.add_argument('--uninstall',
@@ -263,38 +259,21 @@
                       dest='cache',
                       help='Do not use cached information about what files are '
                            'currently on the target device.')
-  parser.add_argument('--show-proguard-warning',
-                      action='store_true',
-                      default=False,
-                      help='Print a warning about proguard being disabled')
-  parser.add_argument('--dont-even-try',
-                      help='Prints this message and exits.')
   parser.add_argument('-v',
                       '--verbose',
                       dest='verbose_count',
                       default=0,
                       action='count',
                       help='Verbose level (multiple times for more)')
-  parser.add_argument('--disable-downgrade',
-                      action='store_false',
-                      default=True,
-                      dest='allow_downgrade',
-                      help='Disable install of apk with lower version number'
-                           'than the version already on the device.')
 
   args = parser.parse_args()
 
   run_tests_helper.SetLogLevel(args.verbose_count)
-  constants.SetBuildType('Debug')
   if args.output_directory:
     constants.SetOutputDirectory(args.output_directory)
 
   devil_chromium.Initialize(output_directory=constants.GetOutDirectory())
 
-  if args.dont_even_try:
-    logging.fatal(args.dont_even_try)
-    return 1
-
   # Retries are annoying when commands fail for legitimate reasons. Might want
   # to enable them if this is ever used on bots though.
   device = device_utils.DeviceUtils.HealthyDevices(
@@ -302,15 +281,14 @@
       default_retries=0,
       enable_device_files_cache=True)[0]
 
-  apk = apk_helper.ToHelper(args.apk_path)
   if args.uninstall:
+    with open(args.json_path) as f:
+      install_dict = json.load(f)
+    apk = apk_helper.ToHelper(install_dict['apk_path'])
     Uninstall(device, apk.GetPackageName(), enable_device_cache=args.cache)
   else:
-    Install(device, apk, split_globs=args.splits, native_libs=args.native_libs,
-            dex_files=args.dex_files, enable_device_cache=args.cache,
-            use_concurrency=args.threading,
-            show_proguard_warning=args.show_proguard_warning,
-            allow_downgrade=args.allow_downgrade)
+    Install(device, args.json_path, enable_device_cache=args.cache,
+            use_concurrency=args.threading)
 
 
 if __name__ == '__main__':
diff --git a/build/android/incremental_install/write_installer_json.py b/build/android/incremental_install/write_installer_json.py
new file mode 100755
index 0000000..66ddd49d
--- /dev/null
+++ b/build/android/incremental_install/write_installer_json.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Writes a .json file with the per-apk details for an incremental install."""
+
+import argparse
+import json
+import os
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
+
+from util import build_utils
+
+
+def _ParseArgs(args):
+  args = build_utils.ExpandFileArgs(args)
+  parser = argparse.ArgumentParser()
+  build_utils.AddDepfileOption(parser)
+  parser.add_argument('--output-path',
+                      help='Output path for .json file.',
+                      required=True)
+  parser.add_argument('--apk-path',
+                      help='Path to .apk relative to output directory.',
+                      required=True)
+  parser.add_argument('--split',
+                      action='append',
+                      dest='split_globs',
+                      default=[],
+                      help='A glob matching the apk splits. '
+                           'Can be specified multiple times.')
+  parser.add_argument('--native-libs-list',
+                      action='append',
+                      default=[],
+                      help='GN-list of paths to native libraries relative to '
+                           'output directory. Can be repeated.')
+  parser.add_argument('--dex-file',
+                      action='append',
+                      default=[],
+                      dest='dex_files',
+                      help='.dex file to include relative to output directory. '
+                           'Can be repeated')
+  parser.add_argument('--dex-file-list',
+                      help='GN-list of dex paths relative to output directory.')
+  parser.add_argument('--show-proguard-warning',
+                      action='store_true',
+                      default=False,
+                      help='Print a warning about proguard being disabled')
+  parser.add_argument('--dont-even-try',
+                      help='Prints the given message and exits.')
+
+  options = parser.parse_args(args)
+  options.dex_files += build_utils.ParseGnList(options.dex_file_list)
+  all_libs = []
+  for gn_list in options.native_libs_list:
+    all_libs.extend(build_utils.ParseGnList(gn_list))
+  options.native_libs_list = all_libs
+  return options
+
+
+def main(args):
+  options = _ParseArgs(args)
+
+  data = {
+      'apk_path': options.apk_path,
+      'native_libs': options.native_libs_list,
+      'dex_files': options.dex_files,
+      'dont_even_try': options.dont_even_try,
+      'show_proguard_warning': options.show_proguard_warning,
+      'split_globs': options.split_globs,
+  }
+
+  with open(options.output_path, 'w') as f:
+    json.dump(data, f, indent=2, sort_keys=True)
+
+  if options.depfile:
+    build_utils.WriteDepfile(options.depfile, options.output_path)
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index 9e4e19a..bb1dbb9 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -281,14 +281,14 @@
         self._exe_dist_dir = exe_dist_dir
 
     incremental_part = ''
-    if args.test_apk_incremental_install_script:
+    if args.test_apk_incremental_install_json:
       incremental_part = '_incremental'
 
     apk_path = os.path.join(
         constants.GetOutDirectory(), '%s_apk' % self._suite,
         '%s-debug%s.apk' % (self._suite, incremental_part))
-    self._test_apk_incremental_install_script = (
-        args.test_apk_incremental_install_script)
+    self._test_apk_incremental_install_json = (
+        args.test_apk_incremental_install_json)
     if not os.path.exists(apk_path):
       self._apk_helper = None
     else:
@@ -423,8 +423,8 @@
     return self._suite
 
   @property
-  def test_apk_incremental_install_script(self):
-    return self._test_apk_incremental_install_script
+  def test_apk_incremental_install_json(self):
+    return self._test_apk_incremental_install_json
 
   @property
   def total_external_shards(self):
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
index fc6a1a7..b6dd7c6 100644
--- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -445,11 +445,11 @@
 
     self._additional_apks = []
     self._apk_under_test = None
-    self._apk_under_test_incremental_install_script = None
+    self._apk_under_test_incremental_install_json = None
     self._package_info = None
     self._suite = None
     self._test_apk = None
-    self._test_apk_incremental_install_script = None
+    self._test_apk_incremental_install_json = None
     self._test_jar = None
     self._test_package = None
     self._junit3_runner_class = None
@@ -536,12 +536,12 @@
 
     self._test_apk = apk_helper.ToHelper(test_apk_path)
 
-    self._apk_under_test_incremental_install_script = (
-        args.apk_under_test_incremental_install_script)
-    self._test_apk_incremental_install_script = (
-        args.test_apk_incremental_install_script)
+    self._apk_under_test_incremental_install_json = (
+        args.apk_under_test_incremental_install_json)
+    self._test_apk_incremental_install_json = (
+        args.test_apk_incremental_install_json)
 
-    if self._test_apk_incremental_install_script:
+    if self._test_apk_incremental_install_json:
       assert self._suite.endswith('_incremental')
       self._suite = self._suite[:-len('_incremental')]
 
@@ -700,8 +700,8 @@
     return self._apk_under_test
 
   @property
-  def apk_under_test_incremental_install_script(self):
-    return self._apk_under_test_incremental_install_script
+  def apk_under_test_incremental_install_json(self):
+    return self._apk_under_test_incremental_install_json
 
   @property
   def coverage_directory(self):
@@ -780,8 +780,8 @@
     return self._test_apk
 
   @property
-  def test_apk_incremental_install_script(self):
-    return self._test_apk_incremental_install_script
+  def test_apk_incremental_install_json(self):
+    return self._test_apk_incremental_install_json
 
   @property
   def test_jar(self):
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index 62ad71f5..4d0dfe0 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -14,6 +14,7 @@
 from devil.android import device_temp_file
 from devil.android import ports
 from devil.utils import reraiser_thread
+from incremental_install import installer
 from pylib import constants
 from pylib.base import base_test_result
 from pylib.gtest import gtest_test_instance
@@ -106,8 +107,8 @@
   def __init__(self, test_instance):
     self._activity = test_instance.activity
     self._apk_helper = test_instance.apk_helper
-    self._test_apk_incremental_install_script = (
-        test_instance.test_apk_incremental_install_script)
+    self._test_apk_incremental_install_json = (
+        test_instance.test_apk_incremental_install_json)
     self._package = test_instance.package
     self._runner = test_instance.runner
     self._permissions = test_instance.permissions
@@ -121,9 +122,9 @@
                           'chromium_tests_root')
 
   def Install(self, device):
-    if self._test_apk_incremental_install_script:
-      local_device_test_run.IncrementalInstall(device, self._apk_helper,
-          self._test_apk_incremental_install_script)
+    if self._test_apk_incremental_install_json:
+      installer.Install(device, self._test_apk_incremental_install_json,
+                        apk=self._apk_helper)
     else:
       device.Install(self._apk_helper, reinstall=True,
                      permissions=self._permissions)
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 14d97b31..51611f2 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -19,6 +19,7 @@
 from devil.android import flag_changer
 from devil.android.tools import system_app
 from devil.utils import reraiser_thread
+from incremental_install import installer
 from pylib import valgrind_tools
 from pylib.android import logdog_logcat_monitor
 from pylib.base import base_test_result
@@ -162,31 +163,30 @@
         return lambda: crash_handler.RetryOnSystemCrash(
             install_helper_internal, dev)
 
-      def incremental_install_helper(apk, script):
+      def incremental_install_helper(apk, json_path):
         @trace_event.traced("apk_path")
         def incremental_install_helper_internal(d, apk_path=apk.path):
           # pylint: disable=unused-argument
-          local_device_test_run.IncrementalInstall(
-              d, apk, script)
+          installer.Install(d, json_path, apk=apk)
         return lambda: crash_handler.RetryOnSystemCrash(
             incremental_install_helper_internal, dev)
 
       if self._test_instance.apk_under_test:
-        if self._test_instance.apk_under_test_incremental_install_script:
+        if self._test_instance.apk_under_test_incremental_install_json:
           steps.append(incremental_install_helper(
                            self._test_instance.apk_under_test,
                            self._test_instance.
-                               apk_under_test_incremental_install_script))
+                               apk_under_test_incremental_install_json))
         else:
           permissions = self._test_instance.apk_under_test.GetPermissions()
           steps.append(install_helper(self._test_instance.apk_under_test,
                                       permissions))
 
-      if self._test_instance.test_apk_incremental_install_script:
+      if self._test_instance.test_apk_incremental_install_json:
         steps.append(incremental_install_helper(
                          self._test_instance.test_apk,
                          self._test_instance.
-                             test_apk_incremental_install_script))
+                             test_apk_incremental_install_json))
       else:
         permissions = self._test_instance.test_apk.GetPermissions()
         steps.append(install_helper(self._test_instance.test_apk,
diff --git a/build/android/pylib/local/device/local_device_test_run.py b/build/android/pylib/local/device/local_device_test_run.py
index 92a9035..94e9c82 100644
--- a/build/android/pylib/local/device/local_device_test_run.py
+++ b/build/android/pylib/local/device/local_device_test_run.py
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import fnmatch
-import imp
 import logging
 import posixpath
 import signal
@@ -26,28 +25,6 @@
   '  Your test may not have run.')
 
 
-def IncrementalInstall(device, apk_helper, installer_script):
-  """Performs an incremental install.
-
-  Args:
-    device: Device to install on.
-    apk_helper: ApkHelper instance for the _incremental.apk.
-    installer_script: Path to the installer script for the incremental apk.
-  """
-  try:
-    install_wrapper = imp.load_source('install_wrapper', installer_script)
-  except IOError:
-    raise Exception('Incremental install script not found: %s\n' %
-                    installer_script)
-  params = install_wrapper.GetInstallParameters()
-
-  from incremental_install import installer
-  installer.Install(device, apk_helper, split_globs=params['splits'],
-                    native_libs=params['native_libs'],
-                    dex_files=params['dex_files'],
-                    permissions=None)  # Auto-grant permissions from manifest.
-
-
 def SubstituteDeviceRoot(device_path, device_root):
   if not device_path:
     return device_root
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index ed0812b..01a0ec53 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -330,7 +330,7 @@
       dest='suite_name', nargs='+', metavar='SUITE_NAME', required=True,
       help='Executable name of the test suite to run.')
   parser.add_argument(
-      '--test-apk-incremental-install-script',
+      '--test-apk-incremental-install-json',
       type=os.path.realpath,
       help='Path to install script for the test apk.')
 
@@ -475,10 +475,10 @@
   # These arguments are suppressed from the help text because they should
   # only ever be specified by an intermediate script.
   parser.add_argument(
-      '--apk-under-test-incremental-install-script',
+      '--apk-under-test-incremental-install-json',
       help=argparse.SUPPRESS)
   parser.add_argument(
-      '--test-apk-incremental-install-script',
+      '--test-apk-incremental-install-json',
       type=os.path.realpath,
       help=argparse.SUPPRESS)
 
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 4084940..1d03946 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -113,9 +113,15 @@
 ../../tools/swarming_client/libs/logdog/stream.py
 ../../tools/swarming_client/libs/logdog/streamname.py
 ../../tools/swarming_client/libs/logdog/varint.py
+../gn_helpers.py
 ../util/lib/common/chrome_test_server_spawner.py
 ../util/lib/common/unittest_util.py
 devil_chromium.py
+gyp/util/__init__.py
+gyp/util/build_utils.py
+gyp/util/md5_check.py
+incremental_install/__init__.py
+incremental_install/installer.py
 pylib/__init__.py
 pylib/android/__init__.py
 pylib/android/logdog_logcat_monitor.py
@@ -179,6 +185,7 @@
 pylib/utils/proguard.py
 pylib/utils/repo_utils.py
 pylib/utils/shared_preference_utils.py
+pylib/utils/time_profile.py
 pylib/valgrind_tools.py
 test_runner.py
 tombstones.py
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 4228f64..8a8f6541 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -303,12 +303,12 @@
         _rebased_apk_path = rebase_path(invoker.apk_path, root_build_dir)
         _rebased_incremental_apk_path =
             rebase_path(invoker.incremental_apk_path, root_build_dir)
-        _rebased_incremental_install_script_path =
-            rebase_path(invoker.incremental_install_script_path, root_build_dir)
+        _rebased_incremental_install_json_path =
+            rebase_path(invoker.incremental_install_json_path, root_build_dir)
         _incremental_allowed =
             defined(invoker.incremental_allowed) && invoker.incremental_allowed
         args += [ "--apk-path=$_rebased_apk_path" ]
-        args += [ "--incremental-install-script-path=$_rebased_incremental_install_script_path" ]
+        args += [ "--incremental-install-json-path=$_rebased_incremental_install_json_path" ]
 
         assert(_rebased_incremental_apk_path != "")  # Mark as used.
         if (_incremental_allowed) {
@@ -644,13 +644,13 @@
     }
     if (_incremental_install) {
       test_runner_args += [
-        "--test-apk-incremental-install-script",
-        "@FileArg($_rebased_apk_build_config:deps_info:incremental_install_script_path)",
+        "--test-apk-incremental-install-json",
+        "@FileArg($_rebased_apk_build_config:deps_info:incremental_install_json_path)",
       ]
       if (defined(invoker.apk_under_test)) {
         test_runner_args += [
-          "--apk-under-test-incremental-install-script",
-          "@FileArg($_rebased_apk_under_test_build_config:deps_info:incremental_install_script_path)",
+          "--apk-under-test-incremental-install-json",
+          "@FileArg($_rebased_apk_under_test_build_config:deps_info:incremental_install_json_path)",
         ]
       }
       test_runner_args += [ "--fast-local-dev" ]
@@ -1197,7 +1197,7 @@
         (is_java_debug || dcheck_always_on)
 
     _desugar = defined(invoker.supports_android) && invoker.supports_android
-    
+
     _deps = []
     _previous_output_jar = _input_jar_path
 
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 648b14fe..a5fb140 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1647,16 +1647,8 @@
     _final_apk_path_no_ext = _final_apk_path_no_ext_list[0]
     assert(_final_apk_path_no_ext != "")  # Mark as used.
 
-    _install_script_name = "install_$_template_name"
-    if (defined(invoker.install_script_name)) {
-      _install_script_name = invoker.install_script_name
-    }
-    _incremental_install_script_path =
-        "${root_out_dir}/bin/${_install_script_name}"
-    if (!incremental_apk_by_default) {
-      _incremental_install_script_path =
-          "${_incremental_install_script_path}_incremental"
-    }
+    _incremental_install_json_path =
+        "$target_gen_dir/$target_name.incremental.json"
 
     _version_code = android_default_version_code
     if (defined(invoker.version_code)) {
@@ -1814,7 +1806,7 @@
       apk_path = _final_apk_path
       incremental_allowed = _incremental_allowed
       incremental_apk_path = "${_final_apk_path_no_ext}_incremental.apk"
-      incremental_install_script_path = _incremental_install_script_path
+      incremental_install_json_path = _incremental_install_json_path
       resources_zip = resources_zip_path
       build_config = _build_config
       android_manifest = _android_root_manifest
@@ -2400,28 +2392,27 @@
       }
     }
 
-    _create_incremental_script_rule_name =
-        "${_template_name}__incremental_script"
-    action(_create_incremental_script_rule_name) {
-      script = "//build/android/incremental_install/create_install_script.py"
+    _write_installer_json_rule_name = "${_template_name}__incremental_json"
+    action(_write_installer_json_rule_name) {
+      script = "//build/android/incremental_install/write_installer_json.py"
       depfile = "$target_gen_dir/$target_name.d"
       deps = [
         _native_libs_file_arg_dep,
       ]
 
       outputs = [
-        _incremental_install_script_path,
+        _incremental_install_json_path,
       ]
 
       _rebased_apk_path_no_ext =
           rebase_path(_final_apk_path_no_ext, root_build_dir)
-      _rebased_incremental_install_script_path =
-          rebase_path(_incremental_install_script_path, root_build_dir)
+      _rebased_incremental_install_json_path =
+          rebase_path(_incremental_install_json_path, root_build_dir)
       _rebased_depfile = rebase_path(depfile, root_build_dir)
       _dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
       args = [
         "--apk-path=${_rebased_apk_path_no_ext}_incremental.apk",
-        "--script-output-path=$_rebased_incremental_install_script_path",
+        "--output-path=$_rebased_incremental_install_json_path",
         "--dex-file=$_rebased_lib_dex_path",
         "--dex-file-list=@FileArg($_dex_arg_key)",
         "--depfile=$_rebased_depfile",
@@ -2465,6 +2456,8 @@
         args = [
           "--script-output-path",
           rebase_path(_generated_script, root_build_dir),
+          "--apk-path",
+          rebase_path(_final_apk_path, root_build_dir),
         ]
         if (defined(invoker.command_line_flags_file)) {
           args += [
@@ -2472,20 +2465,10 @@
             invoker.command_line_flags_file,
           ]
         }
-
         if (_incremental_allowed) {
           args += [
-            "--incremental-apk-path",
-            rebase_path("${_final_apk_path_no_ext}_incremental.apk",
-                        root_build_dir),
-            "--incremental-install-script",
-            rebase_path(_incremental_install_script_path, root_build_dir),
-          ]
-        }
-        if (!incremental_apk_by_default) {
-          args += [
-            "--apk-path",
-            rebase_path(_final_apk_path, root_build_dir),
+            "--incremental-install-json-path",
+            rebase_path(_incremental_install_json_path, root_build_dir),
           ]
         }
       }
@@ -2538,8 +2521,8 @@
         # actual target, but instead loads them at runtime, we need to explicitly
         # depend on them here.
         public_deps = [
-          ":${_create_incremental_script_rule_name}",
           ":${_template_name}__create_incremental",
+          ":${_write_installer_json_rule_name}",
           ":${java_target}",
         ]
 
@@ -2592,7 +2575,6 @@
     testonly = true
     _apk_target_name = "${target_name}__apk"
     _test_runner_target_name = "${target_name}__test_runner_script"
-    _install_script_name = "install_$target_name"
     _dist_ijar_path =
         "$root_build_dir/test.lib.java/" + invoker.apk_name + ".jar"
     _incremental_test_runner_target_name =
@@ -2643,7 +2625,6 @@
       deps = []
       data_deps = []
       forward_variables_from(invoker, "*")
-      install_script_name = _install_script_name
       deps += [ "//testing/android/broker:broker_java" ]
       data_deps += [
         "//build/android/pylib/device/commands",