Initial support for installing APEX via adb.
Test: adb install package-signed.apex (succeeds)
adb install package-unsigned.apex (fails)
Merged-In: I3ac7971ce6923511a7d574291fe9002c5d55fa1b
Change-Id: I53bd15286b62a2a4ae6c2c5746b2ed039bc6ff77
diff --git a/Android.bp b/Android.bp
index fe4fdb2..2b5c513 100644
--- a/Android.bp
+++ b/Android.bp
@@ -675,6 +675,7 @@
],
static_libs: [
+ "apex_aidl_interface-java",
"framework-protos",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.0-java",
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2597f1d..02c35e6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -846,6 +846,14 @@
*/
public static final int INSTALL_VIRTUAL_PRELOAD = 0x00010000;
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * is an APEX package
+ *
+ * @hide
+ */
+ public static final int INSTALL_APEX = 0x00020000;
+
/** @hide */
@IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = {
DONT_KILL_APP
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0b32d1a..6ccd040 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -58,7 +58,6 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SELinux;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -646,8 +645,8 @@
}
try {
- Os.mkdir(stageDir.getAbsolutePath(), 0755);
- Os.chmod(stageDir.getAbsolutePath(), 0755);
+ Os.mkdir(stageDir.getAbsolutePath(), 0775);
+ Os.chmod(stageDir.getAbsolutePath(), 0775);
} catch (ErrnoException e) {
// This purposefully throws if directory already exists
throw new IOException("Failed to prepare session dir: " + stageDir, e);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 81a8367..17daeed 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -42,6 +42,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.apex.IApexService;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
@@ -73,6 +74,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.StorageManager;
@@ -858,12 +860,15 @@
resolveStageDirLocked();
mSealed = true;
-
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
try {
- validateInstallLocked(pkgInfo);
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ validateApexInstallLocked(pkgInfo);
+ } else {
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateApkInstallLocked(pkgInfo);
+ }
} catch (PackageManagerException e) {
throw e;
} catch (Throwable e) {
@@ -942,6 +947,31 @@
Preconditions.checkNotNull(mSigningDetails);
Preconditions.checkNotNull(mResolvedBaseFile);
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ commitApexLocked();
+ } else {
+ commitApkLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void commitApexLocked() throws PackageManagerException {
+ try {
+ IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ apex.installPackage(mResolvedBaseFile.toString());
+ } catch (Throwable e) {
+ // Convert all exceptions into package manager exceptions as only those are handled
+ // in the code above
+ throw new PackageManagerException(e);
+ } finally {
+ destroyInternal();
+ dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "APEX installed", null);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void commitApkLocked() throws PackageManagerException {
if (needToAskForPermissionsLocked()) {
// User needs to accept permissions; give installer an intent they
// can use to involve user.
@@ -1063,6 +1093,57 @@
(params.installFlags & PackageManager.DONT_KILL_APP) != 0;
}
+ @GuardedBy("mLock")
+ private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo)
+ throws PackageManagerException {
+ mResolvedStagedFiles.clear();
+ mResolvedInheritedFiles.clear();
+
+ try {
+ resolveStageDirLocked();
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
+ "Failed to resolve stage location", e);
+ }
+
+ final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
+ if (ArrayUtils.isEmpty(addedFiles)) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ if (addedFiles.length > 1) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Only one APEX file at a time might be installed");
+ }
+ File addedFile = addedFiles[0];
+ final ApkLite apk;
+ try {
+ apk = PackageParser.parseApkLite(
+ addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
+ } catch (PackageParserException e) {
+ throw PackageManagerException.from(e);
+ }
+
+ mPackageName = apk.packageName;
+ mVersionCode = apk.getLongVersionCode();
+ mSigningDetails = apk.signingDetails;
+ mResolvedBaseFile = addedFile;
+
+ assertApkConsistentLocked(String.valueOf(addedFile), apk);
+
+ if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+ try {
+ // STOPSHIP: For APEX we should also implement proper APK Signature verification.
+ mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
+ pkgInfo.applicationInfo.sourceDir,
+ PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Couldn't obtain signatures from base APK");
+ }
+ }
+ }
+
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -1076,7 +1157,7 @@
* {@link PackageManagerService}.
*/
@GuardedBy("mLock")
- private void validateInstallLocked(@Nullable PackageInfo pkgInfo)
+ private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo)
throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 96bf44a..580126f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -915,7 +915,10 @@
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
- if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+ final boolean isApex =
+ (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
+ String splitName = "base." + (isApex ? "apex" : "apk");
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
@@ -2229,6 +2232,9 @@
case "--force-sdk":
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
break;
+ case "--apex":
+ sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}