Allow non-required package verifiers

* Verifiers can be specified in the AndroidManifest.xml

* Those verifiers can respond to the new Intent action

* PackageManager API for those verifiers: verifyPendingInstall

Change-Id: I4892bce2e6984871e6e93c60a1ca0dae145f5df5
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index da97fde0..9625944 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -41,6 +41,8 @@
     public int recommendedInstallLocation;
     public int installLocation;
 
+    public VerifierInfo[] verifiers;
+
     public PackageInfoLite() {
     }
 
@@ -58,6 +60,13 @@
         dest.writeString(packageName);
         dest.writeInt(recommendedInstallLocation);
         dest.writeInt(installLocation);
+
+        if (verifiers == null || verifiers.length == 0) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(verifiers.length);
+            dest.writeTypedArray(verifiers, parcelableFlags);
+        }
     }
 
     public static final Parcelable.Creator<PackageInfoLite> CREATOR
@@ -75,5 +84,13 @@
         packageName = source.readString();
         recommendedInstallLocation = source.readInt();
         installLocation = source.readInt();
+
+        final int verifiersLength = source.readInt();
+        if (verifiersLength == 0) {
+            verifiers = new VerifierInfo[0];
+        } else {
+            verifiers = new VerifierInfo[verifiersLength];
+            source.readTypedArray(verifiers, VerifierInfo.CREATOR);
+        }
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ef7e233..d45a71a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -725,6 +725,16 @@
     public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
 
     /**
+     * Usable by the required verifier as the {@code verificationCode} argument
+     * for {@link PackageManager#verifyPendingInstall} to indicate that it will
+     * allow the installation to proceed without any of the optional verifiers
+     * needing to vote.
+     *
+     * @hide
+     */
+    public static final int VERIFICATION_ALLOW_WITHOUT_SUFFICIENT = 2;
+
+    /**
      * Used as the {@code verificationCode} argument for
      * {@link PackageManager#verifyPendingInstall} to indicate that the calling
      * package verifier allows the installation to proceed.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e7b844c..c30675b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -28,7 +28,9 @@
 import android.os.Bundle;
 import android.os.PatternMatcher;
 import android.util.AttributeSet;
+import android.util.Base64;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.Slog;
 import android.util.TypedValue;
 import com.android.internal.util.XmlUtils;
@@ -40,11 +42,18 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
+import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -150,12 +159,14 @@
      * @hide
      */
     public static class PackageLite {
-        public String packageName;
-        public int installLocation;
-        public String mScanPath;
-        public PackageLite(String packageName, int installLocation) {
+        public final String packageName;
+        public final int installLocation;
+        public final VerifierInfo[] verifiers;
+
+        public PackageLite(String packageName, int installLocation, List<VerifierInfo> verifiers) {
             this.packageName = packageName;
             this.installLocation = installLocation;
+            this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
         }
     }
 
@@ -619,8 +630,9 @@
      * @return PackageLite object with package information or null on failure.
      */
     public static PackageLite parsePackageLite(String packageFilePath, int flags) {
-        XmlResourceParser parser = null;
         AssetManager assmgr = null;
+        final XmlResourceParser parser;
+        final Resources res;
         try {
             assmgr = new AssetManager();
             assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -631,6 +643,9 @@
                 return null;
             }
 
+            final DisplayMetrics metrics = new DisplayMetrics();
+            metrics.setToDefaults();
+            res = new Resources(assmgr, metrics, null);
             parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
         } catch (Exception e) {
             if (assmgr != null) assmgr.close();
@@ -638,11 +653,12 @@
                     + packageFilePath, e);
             return null;
         }
-        AttributeSet attrs = parser;
-        String errors[] = new String[1];
+
+        final AttributeSet attrs = parser;
+        final String errors[] = new String[1];
         PackageLite packageLite = null;
         try {
-            packageLite = parsePackageLite(parser, attrs, flags, errors);
+            packageLite = parsePackageLite(res, parser, attrs, flags, errors);
         } catch (IOException e) {
             Slog.w(TAG, packageFilePath, e);
         } catch (XmlPullParserException e) {
@@ -719,9 +735,9 @@
         return pkgName.intern();
     }
 
-    private static PackageLite parsePackageLite(XmlPullParser parser,
-            AttributeSet attrs, int flags, String[] outError)
-            throws IOException, XmlPullParserException {
+    private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
+            AttributeSet attrs, int flags, String[] outError) throws IOException,
+            XmlPullParserException {
 
         int type;
         while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -759,7 +775,26 @@
                 break;
             }
         }
-        return new PackageLite(pkgName.intern(), installLocation);
+
+        // Only search the tree when the tag is directly below <manifest>
+        final int searchDepth = parser.getDepth() + 1;
+
+        final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
+                final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError);
+                if (verifier != null) {
+                    verifiers.add(verifier);
+                }
+            }
+        }
+
+        return new PackageLite(pkgName.intern(), installLocation, verifiers);
     }
 
     /**
@@ -2691,6 +2726,63 @@
         return data;
     }
 
+    private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
+            AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException,
+            IOException {
+        final TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestPackageVerifier);
+
+        final String packageName = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+
+        final String encodedPublicKey = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
+
+        sa.recycle();
+
+        if (packageName == null || packageName.length() == 0) {
+            Slog.i(TAG, "verifier package name was null; skipping");
+            return null;
+        } else if (encodedPublicKey == null) {
+            Slog.i(TAG, "verifier " + packageName + " public key was null; skipping");
+        }
+
+        EncodedKeySpec keySpec;
+        try {
+            final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT);
+            keySpec = new X509EncodedKeySpec(encoded);
+        } catch (IllegalArgumentException e) {
+            Slog.i(TAG, "Could not parse verifier " + packageName + " public key; invalid Base64");
+            return null;
+        }
+
+        /* First try the key as an RSA key. */
+        try {
+            final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+            final PublicKey publicKey = keyFactory.generatePublic(keySpec);
+            return new VerifierInfo(packageName, publicKey);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "Could not parse public key because RSA isn't included in build");
+            return null;
+        } catch (InvalidKeySpecException e) {
+            // Not a RSA public key.
+        }
+
+        /* Now try it as a DSA key. */
+        try {
+            final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
+            final PublicKey publicKey = keyFactory.generatePublic(keySpec);
+            return new VerifierInfo(packageName, publicKey);
+        } catch (NoSuchAlgorithmException e) {
+            Log.wtf(TAG, "Could not parse public key because DSA isn't included in build");
+            return null;
+        } catch (InvalidKeySpecException e) {
+            // Not a DSA public key.
+        }
+
+        return null;
+    }
+
     private static final String ANDROID_RESOURCES
             = "http://schemas.android.com/apk/res/android";
 
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index c6aefb8..9c9340d 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -19,7 +19,12 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.io.ByteArrayInputStream;
 import java.lang.ref.SoftReference;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.util.Arrays;
 
 /**
@@ -135,6 +140,20 @@
         return bytes;
     }
 
+    /**
+     * Returns the public key for this signature.
+     *
+     * @throws CertificateException when Signature isn't a valid X.509
+     *             certificate; shouldn't happen.
+     * @hide
+     */
+    public PublicKey getPublicKey() throws CertificateException {
+        final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
+        final Certificate cert = certFactory.generateCertificate(bais);
+        return cert.getPublicKey();
+    }
+
     @Override
     public boolean equals(Object obj) {
         try {
diff --git a/core/java/android/content/pm/VerifierInfo.aidl b/core/java/android/content/pm/VerifierInfo.aidl
new file mode 100644
index 0000000..7702d38
--- /dev/null
+++ b/core/java/android/content/pm/VerifierInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable VerifierInfo;
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
new file mode 100644
index 0000000..0a2b283
--- /dev/null
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.PublicKey;
+
+/**
+ * Contains information about a package verifier as used by
+ * {@code PackageManagerService} during package verification.
+ *
+ * @hide
+ */
+public class VerifierInfo implements Parcelable {
+    /** Package name of the verifier. */
+    public final String packageName;
+
+    /** Signatures used to sign the package verifier's package. */
+    public final PublicKey publicKey;
+
+    /**
+     * Creates an object that represents a verifier info object.
+     *
+     * @param packageName the package name in Java-style. Must not be {@code
+     *            null} or empty.
+     * @param publicKey the public key for the signer encoded in Base64. Must
+     *            not be {@code null} or empty.
+     * @throws IllegalArgumentException if either argument is null or empty.
+     */
+    public VerifierInfo(String packageName, PublicKey publicKey) {
+        if (packageName == null || packageName.length() == 0) {
+            throw new IllegalArgumentException("packageName must not be null or empty");
+        } else if (publicKey == null) {
+            throw new IllegalArgumentException("publicKey must not be null");
+        }
+
+        this.packageName = packageName;
+        this.publicKey = publicKey;
+    }
+
+    private VerifierInfo(Parcel source) {
+        packageName = source.readString();
+        publicKey = (PublicKey) source.readSerializable();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(packageName);
+        dest.writeSerializable(publicKey);
+    }
+
+    public static final Parcelable.Creator<VerifierInfo> CREATOR
+            = new Parcelable.Creator<VerifierInfo>() {
+        public VerifierInfo createFromParcel(Parcel source) {
+            return new VerifierInfo(source);
+        }
+
+        public VerifierInfo[] newArray(int size) {
+            return new VerifierInfo[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/tests/coretests/apks/install_verifier_bad/Android.mk b/core/tests/coretests/apks/install_verifier_bad/Android.mk
new file mode 100644
index 0000000..b50cfd04
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_verifier_bad
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml b/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml
new file mode 100644
index 0000000..0170cdd
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.install_verifier_bad">
+
+    <package-verifier android:name="com.android.frameworks.coretests.nonexistent" android:publicKey="Zm9vYmFy" />
+        
+    <application android:hasCode="false">
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml b/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_verifier_good/Android.mk b/core/tests/coretests/apks/install_verifier_good/Android.mk
new file mode 100644
index 0000000..a48a80e
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_verifier_good
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml b/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml
new file mode 100644
index 0000000..90135a5
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.install_verifier_bad">
+
+        <package-verifier android:name="com.android.frameworks.coretests" android:publicKey="MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAnHgFkqwNXTgc3qpl7MimAG42SAxtcgexIBG+UIY6q+K1XQCa33FG1vIgIoDHzU172yYkO4qAbCazSxN1I6SSaCJJBNwBST58Cs8aBch09psDe2AwnZB00kKA4WutKoc0NhlR6vcqSC0JsgSxh14SrJjBqnc9aAC56v3lbVi+2OjaFvmjYAmcN6g0pt/tt7a0SgSeB6Jp/M8sVJbyzzbWTfkKO42PNKO6q0z1M3GrJ3GbO6WHVK0MU/wU4dtF1R4jT7vpPJuk7fnOVCYTUOxTVge/aaL/SqB9tffqIA0JpsG0niFAL4ntEZCJOqtakYDxUugvhaRXU89fwZBxxe7IJwIBAw==" />
+
+    <application android:hasCode="false">
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml b/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="dummy">dummy</string>
+</resources>
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 6e5f856..113f0f7 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -156,6 +156,7 @@
             }
             ret.packageName = pkg.packageName;
             ret.installLocation = pkg.installLocation;
+            ret.verifiers = pkg.verifiers;
 
             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
                     archiveFilePath, flags, threshold);
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 05f7cf0..eb135b7 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -72,6 +72,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.ManifestDigest;
 import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -113,6 +114,8 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -158,6 +161,7 @@
     private static final boolean DEBUG_INTENT_MATCHING = false;
     private static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_APP_DIR_OBSERVER = false;
+    private static final boolean DEBUG_VERIFY = false;
 
     static final boolean MULTIPLE_APPLICATION_UIDS = true;
     private static final int RADIO_UID = Process.PHONE_UID;
@@ -208,6 +212,8 @@
             DEFAULT_CONTAINER_PACKAGE,
             "com.android.defcontainer.DefaultContainerService");
 
+    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
     private static final String LIB_DIR_NAME = "lib";
 
     static final String mTempContainerPrefix = "smdl2tmp";
@@ -349,7 +355,8 @@
     final HashSet<String> mProtectedBroadcasts = new HashSet<String>();
 
     /** List of packages waiting for verification. */
-    final SparseArray<InstallArgs> mPendingVerification = new SparseArray<InstallArgs>();
+    final SparseArray<PackageVerificationState> mPendingVerification
+            = new SparseArray<PackageVerificationState>();
 
     final ArrayList<PackageParser.Package> mDeferredDexOpt =
             new ArrayList<PackageParser.Package>();
@@ -427,6 +434,8 @@
     final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
     int mNextInstallToken = 1;  // nonzero; will be wrapped back to 1 when ++ overflows
 
+    private final String mRequiredVerifierPackage;
+
     class PackageHandler extends Handler {
         private boolean mBound = false;
         final ArrayList<HandlerParams> mPendingInstalls =
@@ -740,9 +749,10 @@
                 } break;
                 case CHECK_PENDING_VERIFICATION: {
                     final int verificationId = msg.arg1;
-                    final InstallArgs args = mPendingVerification.get(verificationId);
+                    final PackageVerificationState state = mPendingVerification.get(verificationId);
 
-                    if (args != null) {
+                    if (state != null) {
+                        final InstallArgs args = state.getInstallArgs();
                         Slog.i(TAG, "Validation timed out for " + args.packageURI.toString());
                         mPendingVerification.remove(verificationId);
 
@@ -756,32 +766,39 @@
                 }
                 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) {
+                    final PackageVerificationState state = mPendingVerification.get(verificationId);
+                    if (state == null) {
                         Slog.w(TAG, "Invalid validation token " + verificationId + " received");
                         break;
                     }
 
-                    mPendingVerification.remove(verificationId);
+                    final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
 
-                    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");
+                    state.setVerifierResponse(response.callerUid, response.code);
+
+                    if (state.isVerificationComplete()) {
+                        mPendingVerification.remove(verificationId);
+
+                        final InstallArgs args = state.getInstallArgs();
+
+                        int ret;
+                        if (state.isInstallAllowed()) {
+                            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;
                         }
-                    } else {
-                        ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+
+                        processPendingInstall(args, ret);
+
+                        mHandler.sendEmptyMessage(MCS_UNBIND);
                     }
 
-                    processPendingInstall(args, ret);
-
-                    mHandler.sendEmptyMessage(MCS_UNBIND);
-
                     break;
                 }
             }
@@ -1134,10 +1151,49 @@
             // are all flushed.  Not really needed, but keeps things nice and
             // tidy.
             Runtime.getRuntime().gc();
+
+            mRequiredVerifierPackage = getRequiredVerifierLPr();
         } // synchronized (mPackages)
         } // synchronized (mInstallLock)
     }
 
+    private String getRequiredVerifierLPr() {
+        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+        final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
+                PackageManager.GET_DISABLED_COMPONENTS);
+
+        String requiredVerifier = null;
+
+        final int N = receivers.size();
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = receivers.get(i);
+
+            if (info.activityInfo == null) {
+                continue;
+            }
+
+            final String packageName = info.activityInfo.packageName;
+
+            final PackageSetting ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                continue;
+            }
+
+            if (!ps.grantedPermissions
+                    .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
+                continue;
+            }
+
+            if (requiredVerifier != null) {
+                throw new RuntimeException("There can be only one required verifier");
+            }
+
+            requiredVerifier = packageName;
+        }
+
+        return requiredVerifier;
+    }
+
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -4857,17 +4913,110 @@
     }
 
     @Override
-    public void verifyPendingInstall(int id, int verificationCode)
-            throws RemoteException {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, null);
-
+    public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
         final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
+        final PackageVerificationResponse response = new PackageVerificationResponse(
+                verificationCode, Binder.getCallingUid());
         msg.arg1 = id;
-        msg.arg2 = verificationCode;
+        msg.obj = response;
         mHandler.sendMessage(msg);
     }
 
+    private ComponentName matchComponentForVerifier(String packageName,
+            List<ResolveInfo> receivers) {
+        ActivityInfo targetReceiver = null;
+
+        final int NR = receivers.size();
+        for (int i = 0; i < NR; i++) {
+            final ResolveInfo info = receivers.get(i);
+            if (info.activityInfo == null) {
+                continue;
+            }
+
+            if (packageName.equals(info.activityInfo.packageName)) {
+                targetReceiver = info.activityInfo;
+                break;
+            }
+        }
+
+        if (targetReceiver == null) {
+            return null;
+        }
+
+        return new ComponentName(targetReceiver.packageName, targetReceiver.name);
+    }
+
+    private List<ComponentName> matchVerifiers(PackageInfoLite pkgInfo,
+            List<ResolveInfo> receivers, final PackageVerificationState verificationState) {
+        if (pkgInfo.verifiers.length == 0) {
+            return null;
+        }
+
+        final int N = pkgInfo.verifiers.length;
+        final List<ComponentName> sufficientVerifiers = new ArrayList<ComponentName>(N + 1);
+        for (int i = 0; i < N; i++) {
+            final VerifierInfo verifierInfo = pkgInfo.verifiers[i];
+
+            final ComponentName comp = matchComponentForVerifier(verifierInfo.packageName,
+                    receivers);
+            if (comp == null) {
+                continue;
+            }
+
+            final int verifierUid = getUidForVerifier(verifierInfo);
+            if (verifierUid == -1) {
+                continue;
+            }
+
+            if (DEBUG_VERIFY) {
+                Slog.d(TAG, "Added sufficient verifier " + verifierInfo.packageName
+                        + " with the correct signature");
+            }
+            sufficientVerifiers.add(comp);
+            verificationState.addSufficientVerifier(verifierUid);
+        }
+
+        return sufficientVerifiers;
+    }
+
+    private int getUidForVerifier(VerifierInfo verifierInfo) {
+        synchronized (mPackages) {
+            final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName);
+            if (pkg == null) {
+                return -1;
+            } else if (pkg.mSignatures.length != 1) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " has more than one signature; ignoring");
+                return -1;
+            }
+
+            /*
+             * If the public key of the package's signature does not match
+             * our expected public key, then this is a different package and
+             * we should skip.
+             */
+
+            final byte[] expectedPublicKey;
+            try {
+                final Signature verifierSig = pkg.mSignatures[0];
+                final PublicKey publicKey = verifierSig.getPublicKey();
+                expectedPublicKey = publicKey.getEncoded();
+            } catch (CertificateException e) {
+                return -1;
+            }
+
+            final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded();
+
+            if (!Arrays.equals(actualPublicKey, expectedPublicKey)) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " does not have the expected public key; ignoring");
+                return -1;
+            }
+
+            return pkg.applicationInfo.uid;
+        }
+    }
+
     public void finishPackageInstall(int token) {
         enforceSystemOrRoot("Only the system is allowed to finish installs");
 
@@ -5237,9 +5386,11 @@
          */
         public void handleStartCopy() throws RemoteException {
             int ret = PackageManager.INSTALL_SUCCEEDED;
-            boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
-            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
-            boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
+            final boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
+            PackageInfoLite pkgLite = null;
+
             if (onInt && onSd) {
                 // Check if both bits are set.
                 Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
@@ -5261,7 +5412,6 @@
                 }
 
                 // Remote call to find out default install location
-                final PackageInfoLite pkgLite;
                 try {
                     mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                             Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -5304,21 +5454,27 @@
             }
 
             final InstallArgs args = createInstallArgs(this);
+            mArgs = args;
+
             if (ret == PackageManager.INSTALL_SUCCEEDED) {
                 /*
                  * 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 int requiredUid = mRequiredVerifierPackage == null ? -1
+                        : getPackageUid(mRequiredVerifierPackage);
+                if (requiredUid != -1 && isVerificationEnabled()) {
+                    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) {
+                    final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
+                            PackageManager.GET_DISABLED_COMPONENTS);
+
+                    if (DEBUG_VERIFY) {
                         Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
-                                + verification.toString());
+                                + verification.toString() + " with " + pkgLite.verifiers.length
+                                + " optional verifiers");
                     }
 
                     final int verificationId = mPendingVerificationToken++;
@@ -5335,35 +5491,70 @@
                                 verificationURI);
                     }
 
-                    mPendingVerification.append(verificationId, args);
+                    final PackageVerificationState verificationState = new PackageVerificationState(
+                            requiredUid, args);
+
+                    mPendingVerification.append(verificationId, verificationState);
+
+                    final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+                            receivers, verificationState);
 
                     /*
-                     * Send the intent to the registered verification agents,
-                     * but only start the verification timeout after the target
-                     * BroadcastReceivers have run.
+                     * If any sufficient verifiers were listed in the package
+                     * manifest, attempt to ask them.
                      */
-                    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);
+                    if (sufficientVerifiers != null) {
+                        final int N = sufficientVerifiers.size();
+                        if (N == 0) {
+                            Slog.i(TAG, "Additional verifiers required, but none installed.");
+                            ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+                        } else {
+                            for (int i = 0; i < N; i++) {
+                                final ComponentName verifierComponent = sufficientVerifiers.get(i);
+
+                                final Intent sufficientIntent = new Intent(verification);
+                                sufficientIntent.setComponent(verifierComponent);
+
+                                mContext.sendBroadcast(sufficientIntent);
+                            }
+                        }
+                    }
+
+                    final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+                            mRequiredVerifierPackage, receivers);
+                    if (ret == PackageManager.INSTALL_SUCCEEDED
+                            && mRequiredVerifierPackage != null) {
+                        /*
+                         * Send the intent to the required verification agent,
+                         * but only start the verification timeout after the
+                         * target BroadcastReceivers have run.
+                         */
+                        verification.setComponent(requiredVerifierComponent);
+                        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);
+
+                        /*
+                         * We don't want the copy to proceed until verification
+                         * succeeds, so null out this field.
+                         */
+                        mArgs = null;
+                    }
                 } else {
-                    // Create copy only if we are not in an erroneous state.
-                    // Remote call to initiate copy using temporary file
-                    mArgs = args;
+                    /*
+                     * No package verification is enabled, so immediately start
+                     * the remote call to initiate copy using temporary file.
+                     */
                     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;
@@ -7549,6 +7740,8 @@
 
         public static final int DUMP_PROVIDERS = 1 << 7;
 
+        public static final int DUMP_VERIFIERS = 1 << 8;
+
         public static final int OPTION_SHOW_FILTERS = 1 << 0;
 
         private int mTypes;
@@ -7641,6 +7834,7 @@
                 pw.println("    p[ackages]: dump installed packages");
                 pw.println("    s[hared-users]: dump shared user IDs");
                 pw.println("    m[essages]: print collected runtime messages");
+                pw.println("    v[erifiers]: print package verifier info");
                 pw.println("    <package.name>: info about given package");
                 return;
             } else if ("-f".equals(opt)) {
@@ -7673,11 +7867,24 @@
                 dumpState.setDump(DumpState.DUMP_PROVIDERS);
             } else if ("m".equals(cmd) || "messages".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_MESSAGES);
+            } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_VERIFIERS);
             }
         }
 
         // reader
         synchronized (mPackages) {
+            if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
+                if (dumpState.onTitlePrinted())
+                    pw.println(" ");
+                pw.println("Verifiers:");
+                pw.print("  Required: ");
+                pw.print(mRequiredVerifierPackage);
+                pw.print(" (uid=");
+                pw.print(getPackageUid(mRequiredVerifierPackage));
+                pw.println(")");
+            }
+
             if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
                 if (dumpState.onTitlePrinted())
                     pw.println(" ");
diff --git a/services/java/com/android/server/pm/PackageVerificationResponse.java b/services/java/com/android/server/pm/PackageVerificationResponse.java
new file mode 100644
index 0000000..b2ae0dd
--- /dev/null
+++ b/services/java/com/android/server/pm/PackageVerificationResponse.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+public class PackageVerificationResponse {
+    public final int code;
+
+    public final int callerUid;
+
+    public PackageVerificationResponse(int code, int callerUid) {
+        this.code = code;
+        this.callerUid = callerUid;
+    }
+}
diff --git a/services/java/com/android/server/pm/PackageVerificationState.java b/services/java/com/android/server/pm/PackageVerificationState.java
new file mode 100644
index 0000000..e5b89c1
--- /dev/null
+++ b/services/java/com/android/server/pm/PackageVerificationState.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.pm.PackageManagerService.InstallArgs;
+
+import android.content.pm.PackageManager;
+import android.util.SparseBooleanArray;
+
+/**
+ * Tracks the package verification state for a particular package. Each package
+ * verification has a required verifier and zero or more sufficient verifiers.
+ * Only one of the sufficient verifier list must return affirmative to allow the
+ * package to be considered verified. If there are zero sufficient verifiers,
+ * then package verification is considered complete.
+ */
+class PackageVerificationState {
+    private final InstallArgs mArgs;
+
+    private final SparseBooleanArray mSufficientVerifierUids;
+
+    private final int mRequiredVerifierUid;
+
+    private boolean mSufficientVerificationComplete;
+
+    private boolean mSufficientVerificationPassed;
+
+    private boolean mRequiredVerificationComplete;
+
+    private boolean mRequiredVerificationPassed;
+
+    /**
+     * Create a new package verification state where {@code requiredVerifierUid}
+     * is the user ID for the package that must reply affirmative before things
+     * can continue.
+     *
+     * @param requiredVerifierUid user ID of required package verifier
+     * @param args
+     */
+    public PackageVerificationState(int requiredVerifierUid, InstallArgs args) {
+        mRequiredVerifierUid = requiredVerifierUid;
+        mArgs = args;
+        mSufficientVerifierUids = new SparseBooleanArray();
+    }
+
+    public InstallArgs getInstallArgs() {
+        return mArgs;
+    }
+
+    /**
+     * Add a verifier which is added to our sufficient list.
+     *
+     * @param uid user ID of sufficient verifier
+     */
+    public void addSufficientVerifier(int uid) {
+        mSufficientVerifierUids.put(uid, true);
+    }
+
+    /**
+     * Should be called when a verification is received from an agent so the
+     * state of the package verification can be tracked.
+     *
+     * @param uid user ID of the verifying agent
+     * @return {@code true} if the verifying agent actually exists in our list
+     */
+    public boolean setVerifierResponse(int uid, int code) {
+        if (uid == mRequiredVerifierUid) {
+            mRequiredVerificationComplete = true;
+            switch (code) {
+                case PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT:
+                    mSufficientVerifierUids.clear();
+                    // fall through
+                case PackageManager.VERIFICATION_ALLOW:
+                    mRequiredVerificationPassed = true;
+                    break;
+                default:
+                    mRequiredVerificationPassed = false;
+            }
+            return true;
+        } else {
+            if (mSufficientVerifierUids.get(uid)) {
+                if (code == PackageManager.VERIFICATION_ALLOW) {
+                    mSufficientVerificationComplete = true;
+                    mSufficientVerificationPassed = true;
+                }
+
+                mSufficientVerifierUids.delete(uid);
+                if (mSufficientVerifierUids.size() == 0) {
+                    mSufficientVerificationComplete = true;
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether verification is considered complete. This means that the
+     * required verifier and at least one of the sufficient verifiers has
+     * returned a positive verification.
+     *
+     * @return {@code true} when verification is considered complete
+     */
+    public boolean isVerificationComplete() {
+        if (!mRequiredVerificationComplete) {
+            return false;
+        }
+
+        if (mSufficientVerifierUids.size() == 0) {
+            return true;
+        }
+
+        return mSufficientVerificationComplete;
+    }
+
+    /**
+     * Returns whether installation should be allowed. This should only be
+     * called after {@link #isVerificationComplete()} returns {@code true}.
+     *
+     * @return {@code true} if installation should be allowed
+     */
+    public boolean isInstallAllowed() {
+        if (!mRequiredVerificationPassed) {
+            return false;
+        }
+
+        if (mSufficientVerificationComplete) {
+            return mSufficientVerificationPassed;
+        }
+
+        return true;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
new file mode 100644
index 0000000..ebd3633
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageManager;
+import com.android.server.pm.PackageVerificationState;
+
+import android.test.AndroidTestCase;
+
+public class PackageVerificationStateTest extends AndroidTestCase {
+    private static final int REQUIRED_UID = 1948;
+
+    private static final int SUFFICIENT_UID_1 = 1005;
+
+    private static final int SUFFICIENT_UID_2 = 8938;
+
+    public void testPackageVerificationState_OnlyRequiredVerifier_AllowedInstall() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_OnlyRequiredVerifier_DeniedInstall() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_REJECT);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertFalse("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_RequiredAndOneSufficient_RequiredDeniedInstall() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_REJECT);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertFalse("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_RequiredAndOneSufficient_SufficientDeniedInstall() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertFalse("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_RequiredAndTwoSufficient_OneSufficientIsEnough() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+        state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_RequiredAndTwoSufficient_SecondSufficientIsEnough() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+        state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(SUFFICIENT_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+        assertTrue("Verification should be considered complete now",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed",
+                state.isInstallAllowed());
+    }
+
+    public void testPackageVerificationState_RequiredAndTwoSufficient_RequiredOverrides() {
+        PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+        state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+        assertFalse("Verification should not be marked as complete yet",
+                state.isVerificationComplete());
+
+        state.setVerifierResponse(REQUIRED_UID,
+                PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
+
+        assertTrue("Verification should be marked as complete immediately",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed",
+                state.isInstallAllowed());
+
+        state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+        assertTrue("Verification should still be marked as completed",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed still",
+                state.isInstallAllowed());
+
+        state.setVerifierResponse(SUFFICIENT_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+        assertTrue("Verification should still be complete",
+                state.isVerificationComplete());
+
+        assertTrue("Installation should be marked as allowed still",
+                state.isInstallAllowed());
+    }
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 3525abe..58680ea 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -36,9 +36,11 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.pm.ManifestDigest;
 import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;