Infrastructure to support package verifier

Allow a package verifier to approve or disapprove of a package being
installed.

Change-Id: Ibfea0f2b1aaa4ab1589a4e59f96144702b9bf94b
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index c980715..0ec007c 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -772,18 +772,33 @@
             }
         }
 
-        String apkFilePath = nextArg();
+        final Uri apkURI;
+        final Uri verificationURI;
+
+        // Populate apkURI, must be present
+        final String apkFilePath = nextArg();
         System.err.println("\tpkg: " + apkFilePath);
-        if (apkFilePath == null) {
+        if (apkFilePath != null) {
+            apkURI = Uri.fromFile(new File(apkFilePath));
+        } else {
             System.err.println("Error: no package specified");
             showUsage();
             return;
         }
 
+        // Populate verificationURI, optionally present
+        final String verificationFilePath = nextArg();
+        if (verificationFilePath != null) {
+            System.err.println("\tver: " + verificationFilePath);
+            verificationURI = Uri.fromFile(new File(verificationFilePath));
+        } else {
+            verificationURI = null;
+        }
+
         PackageInstallObserver obs = new PackageInstallObserver();
         try {
-            mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,
-                    installerPackageName);
+            mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName,
+                    verificationURI, null);
 
             synchronized (obs) {
                 while (!obs.finished) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4cff12f..4b2a8d2 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -41,11 +41,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.ManifestDigest;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
@@ -941,6 +941,27 @@
     }
 
     @Override
+    public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
+            int flags, String installerPackageName, Uri verificationURI,
+            ManifestDigest manifestDigest) {
+        try {
+            mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName,
+                    verificationURI, manifestDigest);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, boolean verified, String failureMessage) {
+        try {
+            mPM.verifyPendingInstall(id, verified, failureMessage);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+    }
+
+    @Override
     public void setInstallerPackageName(String targetPackage,
             String installerPackageName) {
         try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2579ced..8d6cee1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1530,6 +1530,18 @@
     public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
 
     /**
+     * Broadcast Action: Sent to the system package verifier when a package
+     * needs to be verified. The data contains the package URI.
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
+
+    /**
      * Broadcast Action: Resources for a set of packages (which were
      * previously unavailable) are currently
      * available since the media on which they exist is available.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 37b6822..d7607e3 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -30,6 +30,7 @@
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.ManifestDigest;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.PermissionGroupInfo;
@@ -346,4 +347,10 @@
 
     UserInfo createUser(in String name, int flags);
     boolean removeUser(int userId);
+
+    void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer,
+            int flags, in String installerPackageName, in Uri verificationURI,
+            in ManifestDigest manifestDigest);
+
+    void verifyPendingInstall(int id, boolean verified, in String message);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index dd684cd..5c641f1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.ManifestDigest;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -289,11 +290,19 @@
     public static final int INSTALL_EXTERNAL = 0x00000008;
 
     /**
-    * Flag parameter for {@link #installPackage} to indicate that this
-    * package has to be installed on the sdcard.
-    * @hide
-    */
-   public static final int INSTALL_INTERNAL = 0x00000010;
+     * Flag parameter for {@link #installPackage} to indicate that this package
+     * has to be installed on the sdcard.
+     * @hide
+     */
+    public static final int INSTALL_INTERNAL = 0x00000010;
+
+    /**
+     * Flag parameter for {@link #installPackage} to indicate that this install
+     * was initiated via ADB.
+     *
+     * @hide
+     */
+    public static final int INSTALL_FROM_ADB = 0x00000020;
 
     /**
      * Flag parameter for
@@ -483,6 +492,30 @@
     public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
 
     /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package couldn't be installed because the verification timed out.
+     * @hide
+     */
+    public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package couldn't be installed because the verification did not succeed.
+     * @hide
+     */
+    public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
+
+    /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the package changed from what the calling program expected.
+     * @hide
+     */
+    public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
+
+    /**
      * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
      * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
      * if the parser was given a path that is not a file, or does not end with the expected
@@ -995,35 +1028,63 @@
             = "android.content.pm.CLEAN_EXTERNAL_STORAGE";
 
     /**
+     * Extra field name for the URI to a verification file. Passed to a package
+     * verifier.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VERIFICATION_URI = "android.content.pm.extra.VERIFICATION_URI";
+
+    /**
+     * Extra field name for the ID of a package pending verification. Passed to
+     * a package verifier and is used to call back to
+     * {@link PackageManager#verifyPendingInstall(int, boolean)}
+     *
+     * @hide
+     */
+    public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
+
+    /**
+     * Extra field name for the package identifier which is trying to install
+     * the package.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VERIFICATION_INSTALLER_PACKAGE
+            = "android.content.pm.extra.VERIFICATION_INSTALLER_PACKAGE";
+
+    /**
+     * Extra field name for the requested install flags for a package pending
+     * verification. Passed to a package verifier.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VERIFICATION_INSTALL_FLAGS
+            = "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
+
+    /**
      * Retrieve overall information about an application package that is
      * installed on the system.
-     *
-     * <p>Throws {@link NameNotFoundException} if a package with the given
-     * name can not be found on the system.
+     * <p>
+     * Throws {@link NameNotFoundException} if a package with the given name can
+     * not be found on the system.
      *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
-     *                    desired package.
-
+     *            desired package.
      * @param flags Additional option flags. Use any combination of
-     * {@link #GET_ACTIVITIES},
-     * {@link #GET_GIDS},
-     * {@link #GET_CONFIGURATIONS},
-     * {@link #GET_INSTRUMENTATION},
-     * {@link #GET_PERMISSIONS},
-     * {@link #GET_PROVIDERS},
-     * {@link #GET_RECEIVERS},
-     * {@link #GET_SERVICES},
-     * {@link #GET_SIGNATURES},
-     * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
-     *
-     * @return Returns a PackageInfo object containing information about the package.
-     *         If flag GET_UNINSTALLED_PACKAGES is set and  if the package is not
-     *         found in the list of installed applications, the package information is
-     *         retrieved from the list of uninstalled applications(which includes
-     *         installed applications as well as applications
-     *         with data directory ie applications which had been
+     *            {@link #GET_ACTIVITIES}, {@link #GET_GIDS},
+     *            {@link #GET_CONFIGURATIONS}, {@link #GET_INSTRUMENTATION},
+     *            {@link #GET_PERMISSIONS}, {@link #GET_PROVIDERS},
+     *            {@link #GET_RECEIVERS}, {@link #GET_SERVICES},
+     *            {@link #GET_SIGNATURES}, {@link #GET_UNINSTALLED_PACKAGES} to
+     *            modify the data returned.
+     * @return Returns a PackageInfo object containing information about the
+     *         package. If flag GET_UNINSTALLED_PACKAGES is set and if the
+     *         package is not found in the list of installed applications, the
+     *         package information is retrieved from the list of uninstalled
+     *         applications(which includes installed applications as well as
+     *         applications with data directory ie applications which had been
      *         deleted with DONT_DELTE_DATA flag set).
-     *
      * @see #GET_ACTIVITIES
      * @see #GET_GIDS
      * @see #GET_CONFIGURATIONS
@@ -1034,7 +1095,6 @@
      * @see #GET_SERVICES
      * @see #GET_SIGNATURES
      * @see #GET_UNINSTALLED_PACKAGES
-     *
      */
     public abstract PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException;
@@ -2061,6 +2121,46 @@
             String installerPackageName);
 
     /**
+     * Similar to
+     * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
+     * with an extra verification file provided.
+     *
+     * @param packageURI The location of the package file to install. This can
+     *            be a 'file:' or a 'content:' URI.
+     * @param observer An observer callback to get notified when the package
+     *            installation is complete.
+     *            {@link IPackageInstallObserver#packageInstalled(String, int)}
+     *            will be called when that happens. observer may be null to
+     *            indicate that no callback is desired.
+     * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
+     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}
+     *            .
+     * @param installerPackageName Optional package name of the application that
+     *            is performing the installation. This identifies which market
+     *            the package came from.
+     * @param verificationURI The location of the supplementary verification
+     *            file. This can be a 'file:' or a 'content:' URI.
+     * @hide
+     */
+    public abstract void installPackageWithVerification(Uri packageURI,
+            IPackageInstallObserver observer, int flags, String installerPackageName,
+            Uri verificationURI, ManifestDigest manifestDigest);
+
+    /**
+     * Allows a package listening to the
+     * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification
+     * broadcast} to respond to the package manager.
+     *
+     * @param id pending package identifier as passed via the
+     *            {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra
+     * @param verified whether the package was verified as valid
+     * @param failureMessage if verification was false, this is the error
+     *            message that may be shown to the user
+     * @hide
+     */
+    public abstract void verifyPendingInstall(int id, boolean verified, String failureMessage);
+
+    /**
      * Change the installer associated with a given package.  There are limitations
      * on how the installer package can be changed; in particular:
      * <ul>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f8702b9..de06f20 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3960,6 +3960,12 @@
         public static final String WEB_AUTOFILL_QUERY_URL =
             "web_autofill_query_url";
 
+        /** Whether package verification is enabled. {@hide} */
+        public static final String PACKAGE_VERIFIER_ENABLE = "verifier_enable";
+
+        /** Timeout for package verification. {@hide} */
+        public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
+
         /**
          * @hide
          */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b9868db..2dbb0b2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -42,6 +42,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION" />
     <protected-broadcast android:name="android.intent.action.UID_REMOVED" />
     <protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
@@ -1429,6 +1430,24 @@
           android:protectionLevel="signature" />
     <uses-permission android:name="android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE"/>
 
+    <!-- Package verifier needs to have this permission before the PackageManager will
+         trust it to verify packages.
+         @hide
+    -->
+    <permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT"
+        android:label="@string/permlab_packageVerificationAgent"
+        android:description="@string/permdesc_packageVerificationAgent"
+        android:protectionLevel="signatureOrSystem" />
+
+    <!-- Must be required by package verifier receiver, to ensure that only the
+         system can interact with it.
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
+        android:label="@string/permlab_bindPackageVerifier"
+        android:description="@string/permdesc_bindPackageVerifier"
+        android:protectionLevel="signature" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7d6d25c..c70e3d2 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2181,6 +2181,22 @@
         Browser\'s geolocation permissions. Malicious applications
         can use this to allow sending location information to arbitrary web sites.</string>
 
+    <!-- Title of an application permission which allows the application to verify whether
+         a different package is able to be installed by some internal logic. [CHAR LIMIT=40] -->
+    <string name="permlab_packageVerificationAgent">verify packages</string>
+    <!-- Description of an application permission which allows the application to verify whether
+         a different package is able to be installed by some internal heuristic. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_packageVerificationAgent">Allows the application to verify a package is
+        installable.</string>
+
+    <!-- Title of an application permission which allows the application to verify whether
+         a different package is able to be installed by some internal heuristic. [CHAR LIMIT=40] -->
+    <string name="permlab_bindPackageVerifier">bind to a package verifier</string>
+    <!-- Description of an application permission which allows the application to verify whether
+         a different package is able to be installed by some internal heuristic. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
+        package verifiers. Should never be needed for normal applications.</string>
+
     <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
     <string name="save_password_message">Do you want the browser to remember this password?</string>
     <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 177cf41..abb62de 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -38,6 +38,7 @@
 import android.app.IActivityManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.backup.IBackupManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -69,6 +70,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
+import android.content.pm.ManifestDigest;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -188,6 +190,17 @@
 
     static final int REMOVE_CHATTY = 1<<16;
 
+    /**
+     * Whether verification is enabled by default.
+     */
+    private static final boolean DEFAULT_VERIFY_ENABLE = true;
+
+    /**
+     * The default maximum time to wait for the verification agent to return in
+     * milliseconds.
+     */
+    private static final long DEFAULT_VERIFICATION_TIMEOUT = 60 * 1000;
+
     static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
 
     static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
@@ -333,6 +346,12 @@
     // Broadcast actions that are only available to the system.
     final HashSet<String> mProtectedBroadcasts = new HashSet<String>();
 
+    /** List of packages waiting for verification. */
+    final SparseArray<InstallArgs> mPendingVerification = new SparseArray<InstallArgs>();
+
+    /** Token for keys in mPendingVerification. */
+    private int mPendingVerificationToken = 0;
+
     boolean mSystemReady;
     boolean mSafeMode;
     boolean mHasSystemUidErrors;
@@ -364,6 +383,8 @@
     static final int UPDATED_MEDIA_STATUS = 12;
     static final int WRITE_SETTINGS = 13;
     static final int WRITE_STOPPED_PACKAGES = 14;
+    static final int PACKAGE_VERIFIED = 15;
+    static final int CHECK_PENDING_VERIFICATION = 16;
 
     static final int WRITE_SETTINGS_DELAY = 10*1000;  // 10 seconds
 
@@ -444,10 +465,10 @@
         void doHandleMessage(Message msg) {
             switch (msg.what) {
                 case INIT_COPY: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "init_copy");
+                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
                     HandlerParams params = (HandlerParams) msg.obj;
                     int idx = mPendingInstalls.size();
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "idx=" + idx);
+                    if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
                     // If a bind was already initiated we dont really
                     // need to do anything. The pending install
                     // will be processed later on.
@@ -474,7 +495,7 @@
                     break;
                 }
                 case MCS_BOUND: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_bound");
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
                     if (msg.obj != null) {
                         mContainerService = (IMediaContainerService) msg.obj;
                     }
@@ -525,8 +546,8 @@
                     }
                     break;
                 }
-                case MCS_RECONNECT : {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_reconnect");
+                case MCS_RECONNECT: {
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_reconnect");
                     if (mPendingInstalls.size() > 0) {
                         if (mBound) {
                             disconnectService();
@@ -543,27 +564,31 @@
                     }
                     break;
                 }
-                case MCS_UNBIND : {
+                case MCS_UNBIND: {
                     // If there is no actual work left, then time to unbind.
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_unbind");
-                    if (mPendingInstalls.size() == 0) {
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_unbind");
+
+                    if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {
                         if (mBound) {
+                            if (DEBUG_INSTALL) Slog.i(TAG, "calling disconnectService()");
+
                             disconnectService();
                         }
-                    } else {
+                    } else if (mPendingInstalls.size() > 0) {
                         // There are more pending requests in queue.
                         // Just post MCS_BOUND message to trigger processing
                         // of next pending install.
                         mHandler.sendEmptyMessage(MCS_BOUND);
                     }
+
                     break;
                 }
                 case MCS_GIVE_UP: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_giveup too many retries");
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_giveup too many retries");
                     mPendingInstalls.remove(0);
                     break;
                 }
-                case SEND_PENDING_BROADCAST : {
+                case SEND_PENDING_BROADCAST: {
                     String packages[];
                     ArrayList<String> components[];
                     int size = 0;
@@ -707,6 +732,52 @@
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 } break;
+                case CHECK_PENDING_VERIFICATION: {
+                    final int verificationId = msg.arg1;
+                    final InstallArgs args = mPendingVerification.get(verificationId);
+
+                    if (args != null) {
+                        Slog.i(TAG, "Validation timed out for " + args.packageURI.toString());
+                        mPendingVerification.remove(verificationId);
+
+                        int ret = PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT;
+                        processPendingInstall(args, ret);
+
+                        mHandler.sendEmptyMessage(MCS_UNBIND);
+                    }
+
+                    break;
+                }
+                case PACKAGE_VERIFIED: {
+                    final int verificationId = msg.arg1;
+                    final boolean verified = msg.arg2 == 1 ? true : false;
+
+                    final InstallArgs args = mPendingVerification.get(verificationId);
+                    if (args == null) {
+                        Slog.w(TAG, "Invalid validation token " + verificationId + " received");
+                        break;
+                    }
+
+                    mPendingVerification.remove(verificationId);
+
+                    int ret;
+                    if (verified) {
+                        ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                        try {
+                            ret = args.copyApk(mContainerService, true);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Could not contact the ContainerService");
+                        }
+                    } else {
+                        ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+                    }
+
+                    processPendingInstall(args, ret);
+
+                    mHandler.sendEmptyMessage(MCS_UNBIND);
+
+                    break;
+                }
             }
         }
     }
@@ -4693,12 +4764,45 @@
     public void installPackage(
             final Uri packageURI, final IPackageInstallObserver observer, final int flags,
             final String installerPackageName) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.INSTALL_PACKAGES, null);
+        installPackageWithVerification(packageURI, observer, flags, installerPackageName, null,
+                null);
+    }
 
-        Message msg = mHandler.obtainMessage(INIT_COPY);
-        msg.obj = new InstallParams(packageURI, observer, flags,
-                installerPackageName);
+    @Override
+    public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
+            int flags, String installerPackageName, Uri verificationURI,
+            ManifestDigest manifestDigest) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
+
+        final int uid = Binder.getCallingUid();
+
+        final int filteredFlags;
+
+        if (uid == Process.SHELL_UID || uid == 0) {
+            if (DEBUG_INSTALL) {
+                Slog.v(TAG, "Install from ADB");
+            }
+            filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
+        } else {
+            filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
+        }
+
+        final Message msg = mHandler.obtainMessage(INIT_COPY);
+        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
+                verificationURI, manifestDigest);
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, boolean verified, String message)
+            throws RemoteException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, null);
+
+        final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
+        msg.arg1 = id;
+        msg.arg2 = verified ? 1 : 0;
+        msg.obj = message;
         mHandler.sendMessage(msg);
     }
 
@@ -4713,6 +4817,28 @@
         mHandler.sendMessage(msg);
     }
 
+    /**
+     * Get the verification agent timeout.
+     *
+     * @return verification timeout in milliseconds
+     */
+    private long getVerificationTimeout() {
+        return android.provider.Settings.Secure.getLong(mContext.getContentResolver(),
+                android.provider.Settings.Secure.PACKAGE_VERIFIER_TIMEOUT,
+                DEFAULT_VERIFICATION_TIMEOUT);
+    }
+
+    /**
+     * Check whether or not package verification has been enabled.
+     *
+     * @return true if verification should be performed
+     */
+    private boolean isVerificationEnabled() {
+        return android.provider.Settings.Secure.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Secure.PACKAGE_VERIFIER_ENABLE,
+                DEFAULT_VERIFY_ENABLE ? 1 : 0) == 1 ? true : false;
+    }
+
     public void setInstallerPackageName(String targetPackage, String installerPackageName) {
         final int uid = Binder.getCallingUid();
         // writer
@@ -4856,15 +4982,21 @@
         });
     }
 
-    abstract class HandlerParams {
-        final static int MAX_RETRIES = 4;
-        int retry = 0;
+    private abstract class HandlerParams {
+        private static final int MAX_RETRIES = 4;
+
+        /**
+         * Number of times startCopy() has been attempted and had a non-fatal
+         * error.
+         */
+        private int mRetries = 0;
+
         final boolean startCopy() {
             boolean res;
             try {
-                if (DEBUG_SD_INSTALL) Log.i(TAG, "startCopy");
-                retry++;
-                if (retry > MAX_RETRIES) {
+                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+
+                if (++mRetries > MAX_RETRIES) {
                     Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                     mHandler.sendEmptyMessage(MCS_GIVE_UP);
                     handleServiceError();
@@ -4874,7 +5006,7 @@
                     res = true;
                 }
             } catch (RemoteException e) {
-                if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting install MCS_RECONNECT");
+                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                 mHandler.sendEmptyMessage(MCS_RECONNECT);
                 res = false;
             }
@@ -4883,10 +5015,11 @@
         }
 
         final void serviceError() {
-            if (DEBUG_SD_INSTALL) Log.i(TAG, "serviceError");
+            if (DEBUG_INSTALL) Slog.i(TAG, "serviceError");
             handleServiceError();
             handleReturnCode();
         }
+
         abstract void handleStartCopy() throws RemoteException;
         abstract void handleServiceError();
         abstract void handleReturnCode();
@@ -4969,15 +5102,20 @@
         int flags;
         final Uri packageURI;
         final String installerPackageName;
+        final Uri verificationURI;
+        final ManifestDigest manifestDigest;
         private InstallArgs mArgs;
         private int mRet;
+
         InstallParams(Uri packageURI,
                 IPackageInstallObserver observer, int flags,
-                String installerPackageName) {
+                String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest) {
             this.packageURI = packageURI;
             this.flags = flags;
             this.observer = observer;
             this.installerPackageName = installerPackageName;
+            this.verificationURI = verificationURI;
+            this.manifestDigest = manifestDigest;
         }
 
         private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {
@@ -5102,13 +5240,70 @@
                     }
                 }
             }
-            // Create the file args now.
-            mArgs = createInstallArgs(this);
+
+            final InstallArgs args = createInstallArgs(this);
             if (ret == PackageManager.INSTALL_SUCCEEDED) {
-                // Create copy only if we are not in an erroneous state.
-                // Remote call to initiate copy using temporary file
-                ret = mArgs.copyApk(mContainerService, true);
+                /*
+                 * Determine if we have any installed package verifiers. If we
+                 * do, then we'll defer to them to verify the packages.
+                 */
+                final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION,
+                        packageURI);
+                verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+                final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
+                        PackageManager.GET_DISABLED_COMPONENTS);
+                if (isVerificationEnabled() && receivers.size() > 0) {
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
+                                + verification.toString());
+                    }
+
+                    final int verificationId = mPendingVerificationToken++;
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
+                            installerPackageName);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, flags);
+
+                    if (verificationURI != null) {
+                        verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI,
+                                verificationURI);
+                    }
+
+                    mPendingVerification.append(verificationId, args);
+
+                    /*
+                     * Send the intent to the registered verification agents,
+                     * but only start the verification timeout after the target
+                     * BroadcastReceivers have run.
+                     */
+                    mContext.sendOrderedBroadcast(verification,
+                            android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                            new BroadcastReceiver() {
+                                @Override
+                                public void onReceive(Context context, Intent intent) {
+                                    final Message msg = mHandler
+                                            .obtainMessage(CHECK_PENDING_VERIFICATION);
+                                    msg.arg1 = verificationId;
+                                    mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+                                }
+                            },
+                            null, 0, null, null);
+                } else {
+                    // Create copy only if we are not in an erroneous state.
+                    // Remote call to initiate copy using temporary file
+                    mArgs = args;
+                    ret = args.copyApk(mContainerService, true);
+                }
+            } else {
+                // There was an error, so let the processPendingInstall() break
+                // the bad news... uh, through a call in handleReturnCode()
+                mArgs = args;
             }
+
             mRet = ret;
         }
 
@@ -5233,14 +5428,15 @@
         final int flags;
         final Uri packageURI;
         final String installerPackageName;
+        final ManifestDigest manifestDigest;
 
-        InstallArgs(Uri packageURI,
-                IPackageInstallObserver observer, int flags,
-                String installerPackageName) {
+        InstallArgs(Uri packageURI, IPackageInstallObserver observer, int flags,
+                String installerPackageName, ManifestDigest manifestDigest) {
             this.packageURI = packageURI;
             this.flags = flags;
             this.observer = observer;
             this.installerPackageName = installerPackageName;
+            this.manifestDigest = manifestDigest;
         }
 
         abstract void createCopyFile();
@@ -5265,12 +5461,12 @@
         boolean created = false;
 
         FileInstallArgs(InstallParams params) {
-            super(params.packageURI, params.observer,
-                    params.flags, params.installerPackageName);
+            super(params.packageURI, params.observer, params.flags, params.installerPackageName,
+                    params.manifestDigest);
         }
 
         FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) {
-            super(null, null, 0, null);
+            super(null, null, 0, null, null);
             File codeFile = new File(fullCodePath);
             installDir = codeFile.getParentFile();
             codeFileName = fullCodePath;
@@ -5279,7 +5475,7 @@
         }
 
         FileInstallArgs(Uri packageURI, String pkgName, String dataDir) {
-            super(packageURI, null, 0, null);
+            super(packageURI, null, 0, null, null);
             installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
             String apkName = getNextCodePath(null, pkgName, ".apk");
             codeFileName = new File(installDir, apkName + ".apk").getPath();
@@ -5509,12 +5705,12 @@
         String libraryPath;
 
         SdInstallArgs(InstallParams params) {
-            super(params.packageURI, params.observer,
-                    params.flags, params.installerPackageName);
+            super(params.packageURI, params.observer, params.flags, params.installerPackageName,
+                    params.manifestDigest);
         }
 
         SdInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) {
-            super(null, null, PackageManager.INSTALL_EXTERNAL, null);
+            super(null, null, PackageManager.INSTALL_EXTERNAL, null, null);
             // Extract cid from fullCodePath
             int eidx = fullCodePath.lastIndexOf("/");
             String subStr1 = fullCodePath.substring(0, eidx);
@@ -5524,13 +5720,13 @@
         }
 
         SdInstallArgs(String cid) {
-            super(null, null, PackageManager.INSTALL_EXTERNAL, null);
+            super(null, null, PackageManager.INSTALL_EXTERNAL, null, null);
             this.cid = cid;
             setCachePath(PackageHelper.getSdDir(cid));
         }
 
         SdInstallArgs(Uri packageURI, String cid) {
-            super(packageURI, null, PackageManager.INSTALL_EXTERNAL, null);
+            super(packageURI, null, PackageManager.INSTALL_EXTERNAL, null, null);
             this.cid = cid;
         }
 
@@ -6152,6 +6348,26 @@
             res.returnCode = pp.getParseError();
             return;
         }
+
+        /* If the installer passed in a manifest digest, compare it now. */
+        if (args.manifestDigest != null) {
+            if (DEBUG_INSTALL) {
+                final String parsedManifest = pkg.manifestDigest == null ? "null"
+                        : pkg.manifestDigest.toString();
+                Slog.d(TAG, "Comparing manifests: " + args.manifestDigest.toString() + " vs. "
+                        + parsedManifest);
+            }
+
+            if (!args.manifestDigest.equals(pkg.manifestDigest)) {
+                res.returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+                return;
+            }
+        } else if (DEBUG_INSTALL) {
+            final String parsedManifest = pkg.manifestDigest == null
+                    ? "null" : pkg.manifestDigest.toString();
+            Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest);
+        }
+
         // Get rid of all references to package scan path via parser.
         pp = null;
         String oldCodePath = null;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index d84f1e5..501c219 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -37,6 +37,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.ManifestDigest;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -533,4 +534,22 @@
     public void updateUserFlags(int id, int flags) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
+            int flags, String installerPackageName, Uri verificationURI,
+            ManifestDigest manifestDigest) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void verifyPendingInstall(int id, boolean verified, String failureMessage) {
+        throw new UnsupportedOperationException();
+    }
 }