Enable "quick install"
Quick install skips a lot of the normal install steps in order to
dramatically reduce the installation time [eg Twitter normally takes
20s to install. But, installs in under 2.5s with quick install]
The specific optimizations [with caveats]:
1. Use the JIT. Although the oat file is technically created, it
only contains the exploded contents of the APK and does not contain
pre-compiled native binary code. While this improves install time,
it impacts app execution. [saves 17s]
2. Bypass Play verification. Play normally verifies all installs
to ensure we're not installing malware. But, it can take multiple
seconds for Play to collect and send package information to our
backend servers. [saves 2.7s]
3. Reduce JAR file verification. Due to the structure of the JAR
certs, we cannot completely bypass JAR processing. However we skip
the step of verifying every manifest entry. [saves 1.3s]
NOTE: #2 and #3 will only occur on eng/user-debug builds.
Bug: 22848361
Change-Id: I48e77595ad5c13a9534fdb06da67ba8dae2797fb
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6c6871f..56c4fb1 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -28,7 +28,6 @@
import android.util.Slog;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -166,12 +165,7 @@
String oatDir = null;
if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
dexoptType = "dex2oat";
- try {
- oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
- } catch (IOException ioe) {
- Slog.w(TAG, "Unable to create oatDir for package: " + pkg.packageName);
- return DEX_OPT_FAILED;
- }
+ oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
} else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
dexoptType = "patchoat";
} else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
@@ -230,8 +224,7 @@
* cannot be created.
*/
@Nullable
- private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
- throws IOException {
+ private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
if (!pkg.canHaveOatDir()) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6e32e5c..b0e43a5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -481,6 +481,7 @@
throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
"Failed to resolve stage location", e);
}
+ final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
// Verify that stage looks sane with respect to existing application.
// This currently only ensures packageName, versionCode, and certificate
@@ -488,7 +489,10 @@
validateInstallLocked();
Preconditions.checkNotNull(mPackageName);
- Preconditions.checkNotNull(mSignatures);
+ // TODO: fix b/25118622; don't bypass signature check
+ if (!quickInstall) {
+ Preconditions.checkNotNull(mSignatures);
+ }
Preconditions.checkNotNull(mResolvedBaseFile);
if (!mPermissionsAccepted) {
@@ -598,6 +602,7 @@
* {@link PackageManagerService}.
*/
private void validateInstallLocked() throws PackageManagerException {
+ final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
mPackageName = null;
mVersionCode = -1;
mSignatures = null;
@@ -621,7 +626,9 @@
final ApkLite apk;
try {
- apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
+ // TODO: fix b/25118622; always use PARSE_COLLECT_CERTIFICATES
+ final int parseFlags = quickInstall ? 0 : PackageParser.PARSE_COLLECT_CERTIFICATES;
+ apk = PackageParser.parseApkLite(file, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
@@ -742,6 +749,7 @@
}
private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
+ final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0;
if (!mPackageName.equals(apk.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ apk.packageName + " inconsistent with " + mPackageName);
@@ -751,7 +759,8 @@
+ " version code " + apk.versionCode + " inconsistent with "
+ mVersionCode);
}
- if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
+ // TODO: fix b/25118622; don't bypass signature check
+ if (!quickInstall && !Signature.areExactMatch(mSignatures, apk.signatures)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
tag + " signatures are inconsistent");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 99562f3..0c3f4fd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10073,6 +10073,10 @@
if (!DEFAULT_VERIFY_ENABLE) {
return false;
}
+ // TODO: fix b/25118622; don't bypass verification
+ if (Build.IS_DEBUGGABLE && (installFlags & PackageManager.INSTALL_QUICK) != 0) {
+ return false;
+ }
boolean ensureVerifyAppsEnabled = isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS);
@@ -12338,6 +12342,7 @@
final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
|| (args.volumeUuid != null));
+ final boolean quickInstall = ((installFlags & PackageManager.INSTALL_QUICK) != 0);
boolean replace = false;
int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
@@ -12353,7 +12358,8 @@
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
- | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
+ | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
+ | (quickInstall ? PackageParser.PARSE_SKIP_VERIFICATION : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
@@ -12383,7 +12389,6 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
pp.collectCertificates(pkg, parseFlags);
- pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
@@ -12393,6 +12398,16 @@
/* If the installer passed in a manifest digest, compare it now. */
if (args.manifestDigest != null) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectManifestDigest");
+ try {
+ pp.collectManifestDigest(pkg);
+ } catch (PackageParserException e) {
+ res.setError("Failed collect during installPackageLI", e);
+ return;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
if (DEBUG_INSTALL) {
final String parsedManifest = pkg.manifestDigest == null ? "null"
: pkg.manifestDigest.toString();
@@ -12570,7 +12585,7 @@
int result = mPackageDexOptimizer
.performDexOpt(pkg, null /* instruction sets */, false /* forceDex */,
false /* defer */, false /* inclDependencies */,
- true /*bootComplete*/, false /*useJit*/);
+ true /*bootComplete*/, quickInstall /*useJit*/);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath);