Initial support for installing APEX via adb.

Test: adb install package-signed.apex (succeeds)
adb install package-unsigned.apex (fails)
Change-Id: I3ac7971ce6923511a7d574291fe9002c5d55fa1b
diff --git a/Android.bp b/Android.bp
index b715b73..ee159f7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -692,6 +692,7 @@
     ],
 
     static_libs: [
+        "apex_aidl_interface-java",
         "framework-protos",
         "mediaplayer2-protos",
         "android.hidl.base-V1.0-java",
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1f700f7..67b86c0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -855,6 +855,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 51225a7..6e45013 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -43,6 +43,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;
@@ -75,6 +76,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;
@@ -838,12 +840,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) {
@@ -926,6 +931,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 confirm installation; give installer an intent they can use to involve
             // user.
@@ -1047,6 +1077,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.
@@ -1060,7 +1141,7 @@
      * {@link PackageManagerService}.
      */
     @GuardedBy("mLock")
-    private void validateInstallLocked(@Nullable PackageInfo pkgInfo)
+    private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo)
             throws PackageManagerException {
         ApkLite baseApk = null;
         mPackageName = null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e25cca4..38bd172 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -920,7 +920,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;
             }
@@ -2262,6 +2265,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);
             }