Merge "Add verifier device identity"
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4b2a8d2..bd42e34 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -42,6 +42,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.ManifestDigest;
+import android.content.pm.VerifierDeviceIdentity;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -1208,6 +1209,19 @@
         // TODO:
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+        try {
+            return mPM.getVerifierDeviceIdentity();
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+        return null;
+    }
+
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/content/pm/ActivityInfo.aidl b/core/java/android/content/pm/ActivityInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ApplicationInfo.aidl b/core/java/android/content/pm/ApplicationInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/FeatureInfo.aidl b/core/java/android/content/pm/FeatureInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/IPackageDataObserver.aidl b/core/java/android/content/pm/IPackageDataObserver.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 08aef16..5e6e768 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -38,6 +38,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.VerifierDeviceIdentity;
 import android.net.Uri;
 import android.content.IntentSender;
 
@@ -359,4 +360,6 @@
             in ManifestDigest manifestDigest);
 
     void verifyPendingInstall(int id, boolean verified, in String message);
+
+    VerifierDeviceIdentity getVerifierDeviceIdentity();
 }
diff --git a/core/java/android/content/pm/IPackageStatsObserver.aidl b/core/java/android/content/pm/IPackageStatsObserver.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/InstrumentationInfo.aidl b/core/java/android/content/pm/InstrumentationInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ManifestDigest.aidl b/core/java/android/content/pm/ManifestDigest.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PackageInfo.aidl b/core/java/android/content/pm/PackageInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PackageInfoLite.aidl b/core/java/android/content/pm/PackageInfoLite.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b4e3988..dcb6776 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2614,4 +2614,14 @@
     public static int getAppId(int uid) {
         return uid % PER_USER_RANGE;
     }
+
+    /**
+     * Returns the device identity that verifiers can use to associate their
+     * scheme to a particular device. This should not be used by anything other
+     * than a package verifier.
+     *
+     * @return identity that uniquely identifies current device
+     * @hide
+     */
+    public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
 }
diff --git a/core/java/android/content/pm/PackageStats.aidl b/core/java/android/content/pm/PackageStats.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PermissionGroupInfo.aidl b/core/java/android/content/pm/PermissionGroupInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/PermissionInfo.aidl b/core/java/android/content/pm/PermissionInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ProviderInfo.aidl b/core/java/android/content/pm/ProviderInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ResolveInfo.aidl b/core/java/android/content/pm/ResolveInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/ServiceInfo.aidl b/core/java/android/content/pm/ServiceInfo.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.aidl b/core/java/android/content/pm/VerifierDeviceIdentity.aidl
new file mode 100644
index 0000000..eb076ae
--- /dev/null
+++ b/core/java/android/content/pm/VerifierDeviceIdentity.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 VerifierDeviceIdentity;
diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.java b/core/java/android/content/pm/VerifierDeviceIdentity.java
new file mode 100644
index 0000000..bfebe0f
--- /dev/null
+++ b/core/java/android/content/pm/VerifierDeviceIdentity.java
@@ -0,0 +1,231 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * An identity that uniquely identifies a particular device. In this
+ * implementation, the identity is represented as a 64-bit integer encoded to a
+ * 13-character string using RFC 4648's Base32 encoding without the trailing
+ * padding. This makes it easy for users to read and write the code without
+ * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero).
+ *
+ * @hide
+ */
+public class VerifierDeviceIdentity implements Parcelable {
+    /**
+     * Encoded size of a long (64-bit) into Base32. This format will end up
+     * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with
+     * the GROUP_SIZE below.
+     */
+    private static final int LONG_SIZE = 13;
+
+    /**
+     * Size of groupings when outputting as strings. This helps people read it
+     * out and keep track of where they are.
+     */
+    private static final int GROUP_SIZE = 4;
+
+    private final long mIdentity;
+
+    private final String mIdentityString;
+
+    /**
+     * Create a verifier device identity from a long.
+     *
+     * @param identity device identity in a 64-bit integer.
+     * @throws
+     */
+    public VerifierDeviceIdentity(long identity) {
+        mIdentity = identity;
+        mIdentityString = encodeBase32(identity);
+    }
+
+    private VerifierDeviceIdentity(Parcel source) {
+        final long identity = source.readLong();
+
+        mIdentity = identity;
+        mIdentityString = encodeBase32(identity);
+    }
+
+    /**
+     * Generate a new device identity.
+     *
+     * @return random uniformly-distributed device identity
+     */
+    public static VerifierDeviceIdentity generate() {
+        final SecureRandom sr = new SecureRandom();
+        return generate(sr);
+    }
+
+    /**
+     * Generate a new device identity using a provided random number generator
+     * class. This is used for testing.
+     *
+     * @param rng random number generator to retrieve the next long from
+     * @return verifier device identity based on the input from the provided
+     *         random number generator
+     */
+    static VerifierDeviceIdentity generate(Random rng) {
+        long identity = rng.nextLong();
+        return new VerifierDeviceIdentity(identity);
+    }
+
+    private static final char ENCODE[] = {
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+        'Y', 'Z', '2', '3', '4', '5', '6', '7',
+    };
+
+    private static final char SEPARATOR = '-';
+
+    private static final String encodeBase32(long input) {
+        final char[] alphabet = ENCODE;
+
+        /*
+         * Make a character array with room for the separators between each
+         * group.
+         */
+        final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
+
+        int index = encoded.length;
+        for (int i = 0; i < LONG_SIZE; i++) {
+            /*
+             * Make sure we don't put a separator at the beginning. Since we're
+             * building from the rear of the array, we use (LONG_SIZE %
+             * GROUP_SIZE) to make the odd-size group appear at the end instead
+             * of the beginning.
+             */
+            if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
+                encoded[--index] = SEPARATOR;
+            }
+
+            /*
+             * Extract 5 bits of data, then shift it out.
+             */
+            final int group = (int) (input & 0x1F);
+            input >>>= 5;
+
+            encoded[--index] = alphabet[group];
+        }
+
+        return String.valueOf(encoded);
+    }
+
+    // TODO move this out to its own class (android.util.Base32)
+    private static final long decodeBase32(byte[] input) throws IllegalArgumentException {
+        long output = 0L;
+        int numParsed = 0;
+
+        final int N = input.length;
+        for (int i = 0; i < N; i++) {
+            final int group = input[i];
+
+            /*
+             * This essentially does the reverse of the ENCODED alphabet above
+             * without a table. A..Z are 0..25 and 2..7 are 26..31.
+             */
+            final int value;
+            if ('A' <= group && group <= 'Z') {
+                value = group - 'A';
+            } else if ('2' <= group && group <= '7') {
+                value = group - ('2' - 26);
+            } else if (group == SEPARATOR) {
+                continue;
+            } else {
+                throw new IllegalArgumentException("base base-32 character: " + group);
+            }
+
+            output = (output << 5) | value;
+            numParsed++;
+
+            if (numParsed == 1) {
+                if ((value & 0xF) != value) {
+                    throw new IllegalArgumentException("illegal start character; will overflow");
+                }
+            } else if (numParsed > 13) {
+                throw new IllegalArgumentException("too long; should have 13 characters");
+            }
+        }
+
+        if (numParsed != 13) {
+            throw new IllegalArgumentException("too short; should have 13 characters");
+        }
+
+        return output;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) mIdentity;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof VerifierDeviceIdentity)) {
+            return false;
+        }
+
+        final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other;
+        return mIdentity == o.mIdentity;
+    }
+
+    @Override
+    public String toString() {
+        return mIdentityString;
+    }
+
+    public static VerifierDeviceIdentity parse(String deviceIdentity)
+            throws IllegalArgumentException {
+        final byte[] input;
+        try {
+            input = deviceIdentity.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalArgumentException("bad base-32 characters in input");
+        }
+
+        return new VerifierDeviceIdentity(decodeBase32(input));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mIdentity);
+    }
+
+    public static final Parcelable.Creator<VerifierDeviceIdentity> CREATOR
+            = new Parcelable.Creator<VerifierDeviceIdentity>() {
+        public VerifierDeviceIdentity createFromParcel(Parcel source) {
+            return new VerifierDeviceIdentity(source);
+        }
+
+        public VerifierDeviceIdentity[] newArray(int size) {
+            return new VerifierDeviceIdentity[size];
+        }
+    };
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 146466f..cadc895 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -88,6 +88,7 @@
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
+    <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
 
     <!--os storage test permissions -->
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
diff --git a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
index 8922f27..cc8c4a6 100644
--- a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
+++ b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
@@ -1,3 +1,19 @@
+/*
+ * 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;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 68ddcc4..9575ced 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -42,6 +42,7 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
@@ -3121,6 +3122,14 @@
         invokeInstallPackageFail(Uri.fromFile(invalidFile), 0, retCode);
     }
 
+    @SmallTest
+    public void testGetVerifierDeviceIdentity() {
+        PackageManager pm = getPm();
+        VerifierDeviceIdentity id = pm.getVerifierDeviceIdentity();
+
+        assertNotNull("Verifier device identity should not be null", id);
+    }
+
     /*---------- Recommended install location tests ----*/
     /*
      * TODO's
diff --git a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
new file mode 100644
index 0000000..e6a6a26
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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 java.util.Random;
+
+public class VerifierDeviceIdentityTest extends android.test.AndroidTestCase {
+    private static final long TEST_1 = 0x7A5F00FF5A55AAA5L;
+
+    private static final String TEST_1_ENCODED = "HUXY-A75N-FLKV-F";
+
+    private static final long TEST_2 = 0x5A05FF5A05F0A555L;
+
+    private static final long TEST_MAXVALUE = Long.MAX_VALUE;
+
+    private static final String TEST_MAXVALUE_ENCODED = "H777-7777-7777-7";
+
+    private static final long TEST_MINVALUE = Long.MIN_VALUE;
+
+    private static final String TEST_MINVALUE_ENCODED = "IAAA-AAAA-AAAA-A";
+
+    private static final long TEST_ZERO = 0L;
+
+    private static final String TEST_ZERO_ENCODED = "AAAA-AAAA-AAAA-A";
+
+    private static final long TEST_NEGONE = -1L;
+
+    private static final String TEST_NEGONE_ENCODED = "P777-7777-7777-7";
+
+    private static final String TEST_OVERFLOW_ENCODED = "QAAA-AAAA-AAAA-A";
+
+    public void testVerifierDeviceIdentity_Equals_Success() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+        VerifierDeviceIdentity id2 = new VerifierDeviceIdentity(TEST_1);
+
+        assertTrue("The two VerifierDeviceIdentity instances should be equal", id1.equals(id2));
+    }
+
+    public void testVerifierDeviceIdentity_Equals_Failure() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+        VerifierDeviceIdentity id2 = new VerifierDeviceIdentity(TEST_2);
+
+        assertFalse("The two VerifierDeviceIdentity instances should be unique", id1.equals(id2));
+    }
+
+    public void testVerifierDeviceIdentity_HashCode() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+
+        assertEquals("The VerifierDeviceIdentity should have the same hashcode as its identity",
+                (int) TEST_1, id1.hashCode());
+    }
+
+    public void testVerifierDeviceIdentity_ToString_Success() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+
+        assertEquals("The identity should encode correctly to the expected Base 32 string",
+                TEST_1_ENCODED, id1.toString());
+    }
+
+    public void testVerifierDeviceIdentity_ToString_Largest() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_MAXVALUE);
+
+        assertEquals("The identity should encode correctly to the expected Base 32 string",
+                TEST_MAXVALUE_ENCODED, id1.toString());
+    }
+
+    public void testVerifierDeviceIdentity_ToString_Zero() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_ZERO);
+
+        assertEquals("The identity should encode correctly to the expected Base 32 string",
+                TEST_ZERO_ENCODED, id1.toString());
+    }
+
+    public void testVerifierDeviceIdentity_ToString_NegOne() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_NEGONE);
+
+        assertEquals("The identity should encode correctly to the expected Base 32 string",
+                TEST_NEGONE_ENCODED, id1.toString());
+    }
+
+    public void testVerifierDeviceIdentity_ToString_MinValue() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_MINVALUE);
+
+        assertEquals("The identity should encode correctly to the expected Base 32 string",
+                TEST_MINVALUE_ENCODED, id1.toString());
+    }
+
+    public void testVerifierDeviceIdentity_Parcel_ReadNegative() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_MINVALUE);
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeLong(TEST_MINVALUE);
+        parcel.setDataPosition(0);
+
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.CREATOR.createFromParcel(parcel);
+
+        assertEquals("Parcel created should match expected value", id1, id2);
+    }
+
+    public void testVerifierDeviceIdentity_Parcel_Read_Pass() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+
+        Parcel parcel = Parcel.obtain();
+        id1.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.CREATOR.createFromParcel(parcel);
+
+        assertEquals("Original identity and parceled identity should be the same", id1, id2);
+    }
+
+    private static class MockRandom extends Random {
+        private long mNextLong;
+
+        public MockRandom() {
+        }
+
+        public void setNextLong(long nextLong) {
+            mNextLong = nextLong;
+        }
+
+        @Override
+        public long nextLong() {
+            return mNextLong;
+        }
+    }
+
+    public void testVerifierDeviceIdentity_Generate_MinValue() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_MINVALUE);
+
+        MockRandom random = new MockRandom();
+        random.setNextLong(Long.MIN_VALUE);
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.generate(random);
+
+        assertEquals("Identity created from Long.MIN_VALUE and one created from return from RNG"
+                + " should be the same", id1, id2);
+    }
+
+    public void testVerifierDeviceIdentity_Generate_Random() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+
+        MockRandom random = new MockRandom();
+        random.setNextLong(TEST_1);
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.generate(random);
+
+        assertEquals("Identity should end up being same when coming from RNG", id1, id2);
+    }
+
+    public void testVerifierDeviceIdentity_Parse_Normal() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_1);
+
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.parse(TEST_1_ENCODED);
+
+        assertEquals("Parsed device identity should have the same value as original identity",
+                id1, id2);
+    }
+
+    public void testVerifierDeviceIdentity_Parse_MaxValue() {
+        VerifierDeviceIdentity id1 = new VerifierDeviceIdentity(TEST_MAXVALUE);
+
+        VerifierDeviceIdentity id2 = VerifierDeviceIdentity.parse(TEST_MAXVALUE_ENCODED);
+
+        assertEquals("Original max value and parsed max value should be equal", id1, id2);
+    }
+
+    public void testVerifierDeviceIdentity_Parse_TooShort() {
+        try {
+            VerifierDeviceIdentity id = VerifierDeviceIdentity.parse("AAAA-AAAA-AAAA-");
+            fail("Parsing should fail when device identifier is too short");
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+
+    public void testVerifierDeviceIdentity_Parse_WayTooShort() {
+        try {
+            VerifierDeviceIdentity id = VerifierDeviceIdentity.parse("----------------");
+            fail("Parsing should fail when device identifier is too short");
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+
+    public void testVerifierDeviceIdentity_Parse_TooLong() {
+        try {
+            VerifierDeviceIdentity id = VerifierDeviceIdentity.parse("AAAA-AAAA-AAAA-AA");
+            fail("Parsing should fail when device identifier is too long");
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+
+    public void testVerifierDeviceIdentity_Parse_Overflow() {
+        try {
+            VerifierDeviceIdentity id = VerifierDeviceIdentity.parse(TEST_OVERFLOW_ENCODED);
+            fail("Parsing should fail when the value will overflow");
+        } catch (IllegalArgumentException e) {
+            // success
+        }
+    }
+}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 4e5ca8e..9ebdd52 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -71,6 +71,7 @@
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.pm.ManifestDigest;
+import android.content.pm.VerifierDeviceIdentity;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -8405,4 +8406,15 @@
         mUserManager.removeUser(userId);
         return true;
     }
+
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                "Only package verification agents can read the verifier device identity");
+
+        synchronized (mPackages) {
+            return mSettings.getVerifierDeviceIdentityLPw();
+        }
+    }
 }
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index f270003..7cdb5b1 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -39,6 +39,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PermissionInfo;
 import android.content.pm.Signature;
+import android.content.pm.VerifierDeviceIdentity;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -86,7 +87,10 @@
     // used to grant newer permissions one time during a system upgrade.
     int mInternalSdkPlatform;
     int mExternalSdkPlatform;
-    
+
+    /** Device identity for the purpose of package verification. */
+    private VerifierDeviceIdentity mVerifierDeviceIdentity;
+
     // The user's preferred activities associated with particular intent
     // filters.
     final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities =
@@ -865,6 +869,12 @@
             serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
             serializer.endTag(null, "last-platform-version");
             
+            if (mVerifierDeviceIdentity != null) {
+                serializer.startTag(null, "verifier");
+                serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
+                serializer.endTag(null, "verifier");
+            }
+
             serializer.startTag(null, "permission-trees");
             for (BasePermission bp : mPermissionTrees.values()) {
                 writePermissionLPr(serializer, bp);
@@ -1280,6 +1290,14 @@
                         }
                     } catch (NumberFormatException e) {
                     }
+                } else if (tagName.equals("verifier")) {
+                    final String deviceIdentity = parser.getAttributeValue(null, "device");
+                    try {
+                        mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
+                    } catch (IllegalArgumentException e) {
+                        Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
+                                + e.getMessage());
+                    }
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
                             + parser.getName());
@@ -1894,6 +1912,16 @@
         return PackageManagerService.FIRST_APPLICATION_UID + N;
     }
 
+    public VerifierDeviceIdentity getVerifierDeviceIdentityLPw() {
+        if (mVerifierDeviceIdentity == null) {
+            mVerifierDeviceIdentity = VerifierDeviceIdentity.generate();
+
+            writeLPr();
+        }
+
+        return mVerifierDeviceIdentity;
+    }
+
     public PackageSetting getDisabledSystemPkgLPr(String name) {
         PackageSetting ps = mDisabledSysPackages.get(name);
         return ps;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 501c219..f2fb36f 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -38,6 +38,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.ManifestDigest;
+import android.content.pm.VerifierDeviceIdentity;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -552,4 +553,12 @@
     public void verifyPendingInstall(int id, boolean verified, String failureMessage) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+        throw new UnsupportedOperationException();
+    }
 }