parameters: (device, install_json, apk=None, enable_device_cache=False,
              use_concurrency=True, permissions=())
docstring: Installs the given incremental apk and all required supporting files.
  
    Args:
      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.
      permissions: A list of the permissions to grant, or None to grant all
                   non-denylisted permissions in the manifest.
docstring_summary: Installs the given incremental apk and all required supporting files.
docstring_tokens: Installs the given incremental apk and all required supporting files .
function: 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 (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.
      permissions: A list of the permissions to grant, or None to grant all
                   non-denylisted permissions in the manifest.
    """
    if isinstance(install_json, str):
      with open(install_json) as f:
        install_dict = json.load(f)
    else:
      install_dict = install_json
  
    main_timer = time_profile.TimeProfile()
    install_timer = time_profile.TimeProfile()
    push_native_timer = time_profile.TimeProfile()
    merge_dex_timer = time_profile.TimeProfile()
    push_dex_timer = time_profile.TimeProfile()
  
    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)
    dex_staging_dir = os.path.join(constants.GetOutDirectory(),
                                   'incremental-install',
                                   install_dict['apk_path'])
    device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
  
    # Install .apk(s) if any of them have changed.
    def do_install():
      install_timer.Start()
      if split_globs:
        splits = []
        for split_glob in split_globs:
          splits.extend((f for f in glob.glob(split_glob)))
        device.InstallSplitApk(
            apk,
            splits,
            allow_downgrade=True,
            reinstall=True,
            allow_cached_props=True,
            permissions=permissions)
      else:
        device.Install(
            apk, allow_downgrade=True, reinstall=True, permissions=permissions)
      install_timer.Stop(log=False)
  
    # Push .so and .dex files to the device (if they have changed).
    def do_push_files():
  
      def do_push_native():
        push_native_timer.Start()
        if native_libs:
          with build_utils.TempDir() as temp_dir:
            device_lib_dir = posixpath.join(device_incremental_dir, 'lib')
            for path in native_libs:
              # Note: Can't use symlinks as they don't work when
              # "adb push parent_dir" is used (like we do here).
              shutil.copy(path, os.path.join(temp_dir, os.path.basename(path)))
            device.PushChangedFiles([(temp_dir, device_lib_dir)],
                                    delete_device_stale=True)
        push_native_timer.Stop(log=False)
  
      def do_merge_dex():
        merge_dex_timer.Start()
        shards = _AllocateDexShards(dex_files)
        build_utils.MakeDirectory(dex_staging_dir)
        _CreateDexFiles(shards, dex_staging_dir, apk.GetMinSdkVersion(),
                        use_concurrency)
        merge_dex_timer.Stop(log=False)
  
      def do_push_dex():
        push_dex_timer.Start()
        device.PushChangedFiles([(dex_staging_dir, device_dex_dir)],
                                delete_device_stale=True)
        push_dex_timer.Stop(log=False)
  
      _Execute(use_concurrency, do_push_native, do_merge_dex)
      do_push_dex()
  
    def check_device_configured():
      if apk.GetTargetSdkVersion().isalpha():
        # Assume pre-release SDK is always really new.
        target_sdk_version = 99
      else:
        target_sdk_version = int(apk.GetTargetSdkVersion())
      # Beta Q builds apply allowlist to targetSdk=28 as well.
      if target_sdk_version >= 28 and device.build_version_sdk >= 28:
        # In P, there are two settings:
        #  * hidden_api_policy_p_apps
        #  * hidden_api_policy_pre_p_apps
        # In Q, there is just one:
        #  * hidden_api_policy
        if device.build_version_sdk == 28:
          setting_name = 'hidden_api_policy_p_apps'
        else:
          setting_name = 'hidden_api_policy'
        apis_allowed = ''.join(
            device.RunShellCommand(['settings', 'get', 'global', setting_name],
                                   check_return=True))
        if apis_allowed.strip() not in '01':
          msg = """\
  Cannot use incremental installs on Android P+ without first enabling access to
  non-SDK interfaces (https://developer.android.com/preview/non-sdk-q).
  
  To enable access:
     adb -s {0} shell settings put global {1} 0
  To restore back to default:
     adb -s {0} shell settings delete global {1}"""
          raise Exception(msg.format(device.serial, setting_name))
  
    cache_path = _DeviceCachePath(device)
    def restore_cache():
      if not enable_device_cache:
        return
      if os.path.exists(cache_path):
        logging.info('Using device cache: %s', cache_path)
        with open(cache_path) as f:
          device.LoadCacheData(f.read())
        # Delete the cached file so that any exceptions cause it to be cleared.
        os.unlink(cache_path)
      else:
        logging.info('No device cache present: %s', cache_path)
  
    def save_cache():
      if not enable_device_cache:
        return
      with open(cache_path, 'w') as f:
        f.write(device.DumpCacheData())
        logging.info('Wrote device cache: %s', cache_path)
  
    # Create 2 lock files:
    # * install.lock tells the app to pause on start-up (until we release it).
    # * firstrun.lock is used by the app to pause all secondary processes until
    #   the primary process finishes loading the .dex / .so files.
    def create_lock_files():
      # Creates or zeros out lock files.
      cmd = ('D="%s";'
             'mkdir -p $D &&'
             'echo -n >$D/install.lock 2>$D/firstrun.lock')
      device.RunShellCommand(
          cmd % device_incremental_dir, shell=True, check_return=True)
  
    # The firstrun.lock is released by the app itself.
    def release_installer_lock():
      device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir,
                             check_return=True, shell=True)
  
    # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
    # been designed for multi-threading. Enabling only because this is a
    # developer-only tool.
    setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache,
                           check_device_configured)
  
    _Execute(use_concurrency, do_install, do_push_files)
  
    finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache)
  
    logging.info(
        'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, '
        'dex_merge=%s dex_push=%s, finalize=%s)', os.path.basename(apk.path),
        main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(),
        push_native_timer.GetDelta(), merge_dex_timer.GetDelta(),
        push_dex_timer.GetDelta(), finalize_timer.GetDelta())
    if show_proguard_warning:
      logging.warning('Target had proguard enabled, but incremental install uses '
                      'non-proguarded .dex files. Performance characteristics '
                      'may differ.')
function_tokens: def Install ( device , install_json , apk = None , enable_device_cache = False , use_concurrency = True , permissions = ( ) ) : if isinstance ( install_json , str ) : with open ( install_json ) as f : install_dict = json . load ( f ) else : install_dict = install_json main_timer = time_profile . TimeProfile ( ) install_timer = time_profile . TimeProfile ( ) push_native_timer = time_profile . TimeProfile ( ) merge_dex_timer = time_profile . TimeProfile ( ) push_dex_timer = time_profile . TimeProfile ( ) 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 ) dex_staging_dir = os . path . join ( constants . GetOutDirectory ( ) , 'incremental-install' , install_dict [ 'apk_path' ] ) device_dex_dir = posixpath . join ( device_incremental_dir , 'dex' ) # Install .apk(s) if any of them have changed. def do_install ( ) : install_timer . Start ( ) if split_globs : splits = [ ] for split_glob in split_globs : splits . extend ( ( f for f in glob . glob ( split_glob ) ) ) device . InstallSplitApk ( apk , splits , allow_downgrade = True , reinstall = True , allow_cached_props = True , permissions = permissions ) else : device . Install ( apk , allow_downgrade = True , reinstall = True , permissions = permissions ) install_timer . Stop ( log = False ) # Push .so and .dex files to the device (if they have changed). def do_push_files ( ) : def do_push_native ( ) : push_native_timer . Start ( ) if native_libs : with build_utils . TempDir ( ) as temp_dir : device_lib_dir = posixpath . join ( device_incremental_dir , 'lib' ) for path in native_libs : # Note: Can't use symlinks as they don't work when # "adb push parent_dir" is used (like we do here). shutil . copy ( path , os . path . join ( temp_dir , os . path . basename ( path ) ) ) device . PushChangedFiles ( [ ( temp_dir , device_lib_dir ) ] , delete_device_stale = True ) push_native_timer . Stop ( log = False ) def do_merge_dex ( ) : merge_dex_timer . Start ( ) shards = _AllocateDexShards ( dex_files ) build_utils . MakeDirectory ( dex_staging_dir ) _CreateDexFiles ( shards , dex_staging_dir , apk . GetMinSdkVersion ( ) , use_concurrency ) merge_dex_timer . Stop ( log = False ) def do_push_dex ( ) : push_dex_timer . Start ( ) device . PushChangedFiles ( [ ( dex_staging_dir , device_dex_dir ) ] , delete_device_stale = True ) push_dex_timer . Stop ( log = False ) _Execute ( use_concurrency , do_push_native , do_merge_dex ) do_push_dex ( ) def check_device_configured ( ) : if apk . GetTargetSdkVersion ( ) . isalpha ( ) : # Assume pre-release SDK is always really new. target_sdk_version = 99 else : target_sdk_version = int ( apk . GetTargetSdkVersion ( ) ) # Beta Q builds apply allowlist to targetSdk=28 as well. if target_sdk_version >= 28 and device . build_version_sdk >= 28 : # In P, there are two settings: #  * hidden_api_policy_p_apps #  * hidden_api_policy_pre_p_apps # In Q, there is just one: #  * hidden_api_policy if device . build_version_sdk == 28 : setting_name = 'hidden_api_policy_p_apps' else : setting_name = 'hidden_api_policy' apis_allowed = '' . join ( device . RunShellCommand ( [ 'settings' , 'get' , 'global' , setting_name ] , check_return = True ) ) if apis_allowed . strip ( ) not in '01' : msg = """\
  Cannot use incremental installs on Android P+ without first enabling access to
  non-SDK interfaces (https://developer.android.com/preview/non-sdk-q).
  
  To enable access:
     adb -s {0} shell settings put global {1} 0
  To restore back to default:
     adb -s {0} shell settings delete global {1}""" raise Exception ( msg . format ( device . serial , setting_name ) ) cache_path = _DeviceCachePath ( device ) def restore_cache ( ) : if not enable_device_cache : return if os . path . exists ( cache_path ) : logging . info ( 'Using device cache: %s' , cache_path ) with open ( cache_path ) as f : device . LoadCacheData ( f . read ( ) ) # Delete the cached file so that any exceptions cause it to be cleared. os . unlink ( cache_path ) else : logging . info ( 'No device cache present: %s' , cache_path ) def save_cache ( ) : if not enable_device_cache : return with open ( cache_path , 'w' ) as f : f . write ( device . DumpCacheData ( ) ) logging . info ( 'Wrote device cache: %s' , cache_path ) # Create 2 lock files: # * install.lock tells the app to pause on start-up (until we release it). # * firstrun.lock is used by the app to pause all secondary processes until #   the primary process finishes loading the .dex / .so files. def create_lock_files ( ) : # Creates or zeros out lock files. cmd = ( 'D="%s";' 'mkdir -p $D &&' 'echo -n >$D/install.lock 2>$D/firstrun.lock' ) device . RunShellCommand ( cmd % device_incremental_dir , shell = True , check_return = True ) # The firstrun.lock is released by the app itself. def release_installer_lock ( ) : device . RunShellCommand ( 'echo > %s/install.lock' % device_incremental_dir , check_return = True , shell = True ) # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't # been designed for multi-threading. Enabling only because this is a # developer-only tool. setup_timer = _Execute ( use_concurrency , create_lock_files , restore_cache , check_device_configured ) _Execute ( use_concurrency , do_install , do_push_files ) finalize_timer = _Execute ( use_concurrency , release_installer_lock , save_cache ) logging . info ( 'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, ' 'dex_merge=%s dex_push=%s, finalize=%s)' , os . path . basename ( apk . path ) , main_timer . GetDelta ( ) , setup_timer . GetDelta ( ) , install_timer . GetDelta ( ) , push_native_timer . GetDelta ( ) , merge_dex_timer . GetDelta ( ) , push_dex_timer . GetDelta ( ) , finalize_timer . GetDelta ( ) ) if show_proguard_warning : logging . warning ( 'Target had proguard enabled, but incremental install uses ' 'non-proguarded .dex files. Performance characteristics ' 'may differ.' )