Add direct API to get ManifestDigest

This makes it faster for applications that don't want to collect all the
certificates but do want the AndroidManifest.xml digest.

Bug: 8528639
Change-Id: Ide9498d0981188960af194a9568387337c075bcc
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 75505bc..409b5ae 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -18,10 +18,17 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Base64;
+import android.util.Slog;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
-import java.util.jar.Attributes;
+
+import libcore.io.IoUtils;
 
 /**
  * Represents the manifest digest for a package. This is suitable for comparison
@@ -30,17 +37,17 @@
  * @hide
  */
 public class ManifestDigest implements Parcelable {
+    private static final String TAG = "ManifestDigest";
+
     /** The digest of the manifest in our preferred order. */
     private final byte[] mDigest;
 
-    /** Digest field names to look for in preferred order. */
-    private static final String[] DIGEST_TYPES = {
-            "SHA1-Digest", "SHA-Digest", "MD5-Digest",
-    };
-
     /** What we print out first when toString() is called. */
     private static final String TO_STRING_PREFIX = "ManifestDigest {mDigest=";
 
+    /** Digest algorithm to use. */
+    private static final String DIGEST_ALGORITHM = "SHA-256";
+
     ManifestDigest(byte[] digest) {
         mDigest = digest;
     }
@@ -49,26 +56,32 @@
         mDigest = source.createByteArray();
     }
 
-    static ManifestDigest fromAttributes(Attributes attributes) {
-        if (attributes == null) {
+    static ManifestDigest fromInputStream(InputStream fileIs) {
+        if (fileIs == null) {
             return null;
         }
 
-        String encodedDigest = null;
+        final MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(DIGEST_ALGORITHM + " must be available", e);
+        }
 
-        for (int i = 0; i < DIGEST_TYPES.length; i++) {
-            final String value = attributes.getValue(DIGEST_TYPES[i]);
-            if (value != null) {
-                encodedDigest = value;
-                break;
+        final DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fileIs), md);
+        try {
+            byte[] readBuffer = new byte[8192];
+            while (dis.read(readBuffer, 0, readBuffer.length) != -1) {
+                // not using
             }
-        }
-
-        if (encodedDigest == null) {
+        } catch (IOException e) {
+            Slog.w(TAG, "Could not read manifest");
             return null;
+        } finally {
+            IoUtils.closeQuietly(dis);
         }
 
-        final byte[] digest = Base64.decode(encodedDigest, Base64.DEFAULT);
+        final byte[] digest = md.digest();
         return new ManifestDigest(digest);
     }
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 11f9be93..4835d05 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,7 +24,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
@@ -54,10 +53,9 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
-import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
 
 import com.android.internal.util.XmlUtils;
 
@@ -567,6 +565,28 @@
         return pkg;
     }
 
+    /**
+     * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
+     * APK. If it successfully scanned the package and found the
+     * {@code AndroidManifest.xml}, {@code true} is returned.
+     */
+    public boolean collectManifestDigest(Package pkg) {
+        try {
+            final JarFile jarFile = new JarFile(mArchiveSourcePath);
+            try {
+                final ZipEntry je = jarFile.getEntry(ANDROID_MANIFEST_FILENAME);
+                if (je != null) {
+                    pkg.manifestDigest = ManifestDigest.fromInputStream(jarFile.getInputStream(je));
+                }
+            } finally {
+                jarFile.close();
+            }
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     public boolean collectCertificates(Package pkg, int flags) {
         pkg.mSignatures = null;
 
@@ -618,7 +638,6 @@
                 }
             } else {
                 Enumeration<JarEntry> entries = jarFile.entries();
-                final Manifest manifest = jarFile.getManifest();
                 while (entries.hasMoreElements()) {
                     final JarEntry je = entries.nextElement();
                     if (je.isDirectory()) continue;
@@ -629,8 +648,8 @@
                         continue;
 
                     if (ANDROID_MANIFEST_FILENAME.equals(name)) {
-                        final Attributes attributes = manifest.getAttributes(name);
-                        pkg.manifestDigest = ManifestDigest.fromAttributes(attributes);
+                        pkg.manifestDigest =
+                                ManifestDigest.fromInputStream(jarFile.getInputStream(je));
                     }
 
                     final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
diff --git a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
index cc8c4a6..37495e1 100644
--- a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
+++ b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
@@ -18,64 +18,51 @@
 
 import android.os.Parcel;
 import android.test.AndroidTestCase;
-import android.util.Base64;
 
-import java.util.jar.Attributes;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
 
 public class ManifestDigestTest extends AndroidTestCase {
-    private static final byte[] DIGEST_1 = {
+    private static final byte[] MESSAGE_1 = {
             (byte) 0x00, (byte) 0xAA, (byte) 0x55, (byte) 0xFF
     };
 
-    private static final String DIGEST_1_STR = Base64.encodeToString(DIGEST_1, Base64.DEFAULT);
-
-    private static final byte[] DIGEST_2 = {
-            (byte) 0x0A, (byte) 0xA5, (byte) 0xF0, (byte) 0x5A
-    };
-
-    private static final String DIGEST_2_STR = Base64.encodeToString(DIGEST_2, Base64.DEFAULT);
-
-    private static final Attributes.Name SHA1_DIGEST = new Attributes.Name("SHA1-Digest");
-
-    private static final Attributes.Name MD5_DIGEST = new Attributes.Name("MD5-Digest");
-
-    public void testManifestDigest_FromAttributes_Null() {
+    public void testManifestDigest_FromInputStream_Null() {
         assertNull("Attributes were null, so ManifestDigest.fromAttributes should return null",
-                ManifestDigest.fromAttributes(null));
+                ManifestDigest.fromInputStream(null));
     }
 
-    public void testManifestDigest_FromAttributes_NoAttributes() {
-        Attributes a = new Attributes();
+    public void testManifestDigest_FromInputStream_ThrowsIoException() {
+        InputStream is = new InputStream() {
+            @Override
+            public int read() throws IOException {
+                throw new IOException();
+            }
+        };
 
-        assertNull("There were no attributes to extract, so ManifestDigest should be null",
-                ManifestDigest.fromAttributes(a));
+        assertNull("InputStream threw exception, so ManifestDigest should be null",
+                ManifestDigest.fromInputStream(is));
     }
 
-    public void testManifestDigest_FromAttributes_SHA1PreferredOverMD5() {
-        Attributes a = new Attributes();
-        a.put(SHA1_DIGEST, DIGEST_1_STR);
+    public void testManifestDigest_Equals() throws Exception {
+        InputStream is = new ByteArrayInputStream(MESSAGE_1);
 
-        a.put(MD5_DIGEST, DIGEST_2_STR);
+        ManifestDigest expected =
+                new ManifestDigest(MessageDigest.getInstance("SHA-256").digest(MESSAGE_1));
 
-        ManifestDigest fromAttributes = ManifestDigest.fromAttributes(a);
+        ManifestDigest actual = ManifestDigest.fromInputStream(is);
+        assertEquals(expected, actual);
 
-        assertNotNull("A valid ManifestDigest should be returned", fromAttributes);
-
-        ManifestDigest created = new ManifestDigest(DIGEST_1);
-
-        assertEquals("SHA-1 should be preferred over MD5: " + created.toString() + " vs. "
-                + fromAttributes.toString(), created, fromAttributes);
-
-        assertEquals("Hash codes should be the same: " + created.toString() + " vs. "
-                + fromAttributes.toString(), created.hashCode(), fromAttributes
-                .hashCode());
+        ManifestDigest unexpected = new ManifestDigest(new byte[0]);
+        assertFalse(unexpected.equals(actual));
     }
 
-    public void testManifestDigest_Parcel() {
-        Attributes a = new Attributes();
-        a.put(SHA1_DIGEST, DIGEST_1_STR);
+    public void testManifestDigest_Parcel() throws Exception {
+        InputStream is = new ByteArrayInputStream(MESSAGE_1);
 
-        ManifestDigest digest = ManifestDigest.fromAttributes(a);
+        ManifestDigest digest = ManifestDigest.fromInputStream(is);
 
         Parcel p = Parcel.obtain();
         digest.writeToParcel(p, 0);