Make the installer script support incremental installation with split .apks (only for devices with Android M). Until now, we always reinstalled every split .apk. It was simple, but also not very fast. -- MOS_MIGRATED_REVID=101120400
diff --git a/tools/android/incremental_install.py b/tools/android/incremental_install.py index aa2f710..088af54 100644 --- a/tools/android/incremental_install.py +++ b/tools/android/incremental_install.py
@@ -174,7 +174,7 @@ def GetInstallTime(self, package): """Get the installation time of a package.""" _, stdout, _, _ = self._Shell("dumpsys package %s" % package) - match = re.search("lastUpdateTime=(.*)$", stdout, re.MULTILINE) + match = re.search("firstInstallTime=(.*)$", stdout, re.MULTILINE) if match: return match.group(1) else: @@ -240,6 +240,12 @@ if "Success" not in stderr and "Success" not in stdout: raise AdbError(args, ret, stdout, stderr) + def Uninstall(self, pkg): + """Invoke 'adb uninstall'.""" + self._Exec(["uninstall", pkg]) + # No error checking. If this fails, we assume that the app was not installed + # in the first place. + def Delete(self, remote): """Delete the given file (or directory) on the device.""" self.DeleteMultiple([remote]) @@ -586,6 +592,71 @@ "'mobile-install'?" % app_package) +def SplitIncrementalInstall(adb, app_package, execroot, split_main_apk, + split_apks): + """Does incremental installation using split packages.""" + app_dir = os.path.join(DEVICE_DIRECTORY, app_package) + device_manifest_path = "%s/split_manifest" % app_dir + device_manifest = adb.Pull(device_manifest_path) + expected_timestamp = adb.Pull("%s/install_timestamp" % app_dir) + actual_timestamp = adb.GetInstallTime(app_package) + device_checksums = {} + if device_manifest is not None: + for manifest_line in device_manifest.split("\n"): + if manifest_line: + name, checksum = manifest_line.split(" ") + device_checksums[name] = checksum + + install_checksums = {} + install_checksums["__MAIN__"] = Checksum( + os.path.join(execroot, split_main_apk)) + for apk in split_apks: + install_checksums[apk] = Checksum(os.path.join(execroot, apk)) + + reinstall_main = False + if (device_manifest is None or actual_timestamp is None or + actual_timestamp != expected_timestamp or + install_checksums["__MAIN__"] != device_checksums["__MAIN__"] or + set(device_checksums.keys()) != set(install_checksums.keys())): + # The main app is not up to date or not present or something happened + # with the on-device manifest. Start from scratch. Notably, we cannot + # uninstall a split package, so if the set of packages changes, we also + # need to do a full reinstall. + reinstall_main = True + device_checksums = {} + + apks_to_update = [ + apk for apk in split_apks if + apk not in device_checksums or + device_checksums[apk] != install_checksums[apk]] + + if not apks_to_update and not reinstall_main: + # Nothing to do + return + + # Delete the device manifest so that if something goes wrong, we do a full + # reinstall next time + adb.Delete(device_manifest_path) + + if reinstall_main: + logging.info("Installing main APK...") + adb.Uninstall(app_package) + adb.InstallMultiple(os.path.join(execroot, split_main_apk)) + adb.PushString( + adb.GetInstallTime(app_package), + "%s/install_timestamp" % app_dir).result() + + logging.info("Reinstalling %s APKs...", len(apks_to_update)) + + for apk in apks_to_update: + adb.InstallMultiple(os.path.join(execroot, apk), app_package) + + install_manifest = [ + name + " " + checksum for name, checksum in install_checksums.iteritems()] + adb.PushString("\n".join(install_manifest), + "%s/split_manifest" % app_dir).result() + + def IncrementalInstall(adb_path, execroot, stub_datafile, output_marker, adb_jobs, start_type, dexmanifest=None, apk=None, native_libs=None, resource_apk=None, @@ -615,11 +686,8 @@ app_package = GetAppPackage(os.path.join(execroot, stub_datafile)) app_dir = os.path.join(DEVICE_DIRECTORY, app_package) if split_main_apk: - adb.InstallMultiple(os.path.join(execroot, split_main_apk)) - for split_apk in split_apks: - # TODO(build-team): This always reinstalls everything, which defeats the - # purpose of this whole system. - adb.InstallMultiple(os.path.join(execroot, split_apk), app_package) + SplitIncrementalInstall(adb, app_package, execroot, split_main_apk, + split_apks) else: if not apk: VerifyInstallTimestamp(adb, app_package)
diff --git a/tools/android/incremental_install_test.py b/tools/android/incremental_install_test.py index c92fae7..87500fc 100644 --- a/tools/android/incremental_install_test.py +++ b/tools/android/incremental_install_test.py
@@ -28,9 +28,10 @@ def __init__(self): # Map of file name -> contents. self.files = {} + self.split_apks = set() self._error = None self.package_timestamp = None - self._last_package_timestamp = 0 + self._last_package_timestamp = 1 self.shell_cmdlns = [] self.abi = "armeabi-v7a" @@ -64,6 +65,19 @@ self.package_timestamp = self._last_package_timestamp self._last_package_timestamp += 1 return self._CreatePopenMock(0, "Success", "") + elif cmd == "install-multiple": + if args[3] == "-p": + with open(args[5]) as f: + content = f.read() + self.split_apks.add(content) + else: + self.package_timestamp = self._last_package_timestamp + self._last_package_timestamp += 1 + return self._CreatePopenMock(0, "Success", "") + elif cmd == "uninstall": + self._CreatePopenMock(0, "Success", "") + self.split_apks = set() + self.package_timestamp = None elif cmd == "shell": # "/test/adb shell ..." # mkdir, rm, am (application manager), or monkey @@ -74,7 +88,7 @@ elif shell_cmdln.startswith("dumpsys package "): return self._CreatePopenMock( 0, - "lastUpdateTime=%s" % self.package_timestamp, + "firstInstallTime=%s" % self.package_timestamp, "") elif shell_cmdln.startswith("rm"): file_path = shell_cmdln.split()[2] @@ -161,8 +175,11 @@ self._mock_adb.files.pop(self._GetDeviceAppPath(f), None) def _CallIncrementalInstall(self, incremental, native_libs=None, + split_main_apk=None, split_apks=None, start_type="no"): - if incremental: + if split_main_apk: + apk = split_main_apk + elif incremental: apk = None else: apk = self._APK @@ -174,6 +191,8 @@ dexmanifest=self._DEXMANIFEST, apk=apk, resource_apk=self._RESOURCE_APK, + split_main_apk=split_main_apk, + split_apks=split_apks, native_libs=native_libs, output_marker=self._OUTPUT_MARKER, adb_jobs=1, @@ -201,6 +220,50 @@ self.assertEquals("content3", self._GetDeviceFile("dex/ip3")) self.assertEquals("resource apk", self._GetDeviceFile("resources.ap_")) + def testSplitInstallToPristineDevice(self): + with open("split1", "w") as f: + f.write("split_content1") + + with open("main", "w") as f: + f.write("main_Content") + + self._CallIncrementalInstall( + incremental=False, split_main_apk="main", split_apks=["split1"]) + self.assertEquals(set(["split_content1"]), self._mock_adb.split_apks) + + def testSplitInstallUnchanged(self): + with open("split1", "w") as f: + f.write("split_content1") + + with open("main", "w") as f: + f.write("main_Content") + + self._CallIncrementalInstall( + incremental=False, split_main_apk="main", split_apks=["split1"]) + self.assertEquals(set(["split_content1"]), self._mock_adb.split_apks) + self._mock_adb.split_apks = set() + self._CallIncrementalInstall( + incremental=False, split_main_apk="main", split_apks=["split1"]) + self.assertEquals(set([]), self._mock_adb.split_apks) + + def testSplitInstallChanges(self): + with open("split1", "w") as f: + f.write("split_content1") + + with open("main", "w") as f: + f.write("main_Content") + + self._CallIncrementalInstall( + incremental=False, split_main_apk="main", split_apks=["split1"]) + self.assertEquals(set(["split_content1"]), self._mock_adb.split_apks) + + with open("split1", "w") as f: + f.write("split_content2") + self._mock_adb.split_apks = set() + self._CallIncrementalInstall( + incremental=False, split_main_apk="main", split_apks=["split1"]) + self.assertEquals(set(["split_content2"]), self._mock_adb.split_apks) + def testMissingNativeManifestWithIncrementalInstall(self): self._CreateZip() with open("liba.so", "w") as f: @@ -356,8 +419,8 @@ "zip1 zip2 ip2 1") self._PutDeviceFile("dex/ip1", "content1") self._PutDeviceFile("dex/ip2", "content2") - self._PutDeviceFile("install_timestamp", "0") - self._mock_adb.package_timestamp = "0" + self._PutDeviceFile("install_timestamp", "1") + self._mock_adb.package_timestamp = "1" self._CreateZip("zip1", ("zp1", "content1")) self._CreateLocalManifest("zip1 zp1 ip1 0")