New API w/ multiple prefixes

Use the new API that contains multiple hash prefixes and a mask. Also
do some small refactoring necessary to handle multiple prefixes and
use a common implementation of the hash generation

Change-Id: Ib52f767ea6aadc30c67c5bdee949e9f9c5f04e44
diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java
index b87731b..2b9cfa0 100644
--- a/core/java/android/content/pm/EphemeralResolveInfo.java
+++ b/core/java/android/content/pm/EphemeralResolveInfo.java
@@ -37,10 +37,7 @@
     /** Algorithm that will be used to generate the domain digest */
     public static final String SHA_ALGORITHM = "SHA-256";
 
-    /** Full digest of the domain hash */
-    private final byte[] mDigestBytes;
-    /** The first 4 bytes of the domain hash */
-    private final int mDigestPrefix;
+    private final EphemeralDigest mDigest;
     private final String mPackageName;
     /** The filters used to match domain */
     private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
@@ -55,29 +52,23 @@
             throw new IllegalArgumentException();
         }
 
-        mDigestBytes = generateDigest(uri);
-        mDigestPrefix =
-                (mDigestBytes[0] & 0xFF) << 24
-                | (mDigestBytes[1] & 0xFF) << 16
-                | (mDigestBytes[2] & 0xFF) << 8
-                | (mDigestBytes[3] & 0xFF) << 0;
+        mDigest = new EphemeralDigest(uri, -1);
         mFilters.addAll(filters);
         mPackageName = packageName;
     }
 
     EphemeralResolveInfo(Parcel in) {
-        mDigestBytes = in.createByteArray();
-        mDigestPrefix = in.readInt();
+        mDigest = in.readParcelable(null /*loader*/);
         mPackageName = in.readString();
         in.readList(mFilters, null /*loader*/);
     }
 
     public byte[] getDigestBytes() {
-        return mDigestBytes;
+        return mDigest.getDigestBytes()[0];
     }
 
     public int getDigestPrefix() {
-        return mDigestPrefix;
+        return mDigest.getDigestPrefix()[0];
     }
 
     public String getPackageName() {
@@ -88,16 +79,6 @@
         return mFilters;
     }
 
-    private static byte[] generateDigest(Uri uri) {
-        try {
-            final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
-            final byte[] hostBytes = uri.getHost().getBytes();
-            return digest.digest(hostBytes);
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("could not find digest algorithm");
-        }
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -105,8 +86,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeByteArray(mDigestBytes);
-        out.writeInt(mDigestPrefix);
+        out.writeParcelable(mDigest, flags);
         out.writeString(mPackageName);
         out.writeList(mFilters);
     }
@@ -136,4 +116,120 @@
             return mResolveInfo;
         }
     }
+
+    /**
+     * Helper class to generate and store each of the digests and prefixes
+     * sent to the Ephemeral Resolver.
+     * <p>
+     * Since intent filters may want to handle multiple hosts within a
+     * domain [eg “*.google.com”], the resolver is presented with multiple
+     * hash prefixes. For example, "a.b.c.d.e" generates digests for
+     * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e".
+     *
+     * @hide
+     */
+    public static final class EphemeralDigest implements Parcelable {
+        /** Full digest of the domain hashes */
+        private final byte[][] mDigestBytes;
+        /** The first 4 bytes of the domain hashes */
+        private final int[] mDigestPrefix;
+
+        public EphemeralDigest(@NonNull Uri uri, int maxDigests) {
+            if (uri == null) {
+                throw new IllegalArgumentException();
+            }
+            mDigestBytes = generateDigest(uri, maxDigests);
+            mDigestPrefix = new int[mDigestBytes.length];
+            for (int i = 0; i < mDigestBytes.length; i++) {
+                mDigestPrefix[i] =
+                        (mDigestBytes[i][0] & 0xFF) << 24
+                        | (mDigestBytes[i][1] & 0xFF) << 16
+                        | (mDigestBytes[i][2] & 0xFF) << 8
+                        | (mDigestBytes[i][3] & 0xFF) << 0;
+            }
+        }
+
+        private static byte[][] generateDigest(Uri uri, int maxDigests) {
+            ArrayList<byte[]> digests = new ArrayList<>();
+            try {
+                final String host = uri.getHost();
+                final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+                if (maxDigests <= 0) {
+                    final byte[] hostBytes = host.getBytes();
+                    digests.add(digest.digest(hostBytes));
+                } else {
+                    int prevDot = host.lastIndexOf('.');
+                    prevDot = host.lastIndexOf('.', prevDot - 1);
+                    // shortcut for short URLs
+                    if (prevDot < 0) {
+                        digests.add(digest.digest(host.getBytes()));
+                    } else {
+                        byte[] hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
+                        digests.add(digest.digest(hostBytes));
+                        int digestCount = 1;
+                        while (prevDot >= 0 && digestCount < maxDigests) {
+                            prevDot = host.lastIndexOf('.', prevDot - 1);
+                            hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
+                            digests.add(digest.digest(hostBytes));
+                            digestCount++;
+                        }
+                    }
+                }
+            } catch (NoSuchAlgorithmException e) {
+                throw new IllegalStateException("could not find digest algorithm");
+            }
+            return digests.toArray(new byte[digests.size()][]);
+        }
+
+        EphemeralDigest(Parcel in) {
+            final int digestCount = in.readInt();
+            if (digestCount == -1) {
+                mDigestBytes = null;
+            } else {
+                mDigestBytes = new byte[digestCount][];
+                for (int i = 0; i < digestCount; i++) {
+                    mDigestBytes[i] = in.createByteArray();
+                }
+            }
+            mDigestPrefix = in.createIntArray();
+        }
+
+        public byte[][] getDigestBytes() {
+            return mDigestBytes;
+        }
+
+        public int[] getDigestPrefix() {
+            return mDigestPrefix;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            if (mDigestBytes == null) {
+                out.writeInt(-1);
+            } else {
+                out.writeInt(mDigestBytes.length);
+                for (int i = 0; i < mDigestBytes.length; i++) {
+                    out.writeByteArray(mDigestBytes[i]);
+                }
+            }
+            out.writeIntArray(mDigestPrefix);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<EphemeralDigest> CREATOR =
+                new Parcelable.Creator<EphemeralDigest>() {
+            public EphemeralDigest createFromParcel(Parcel in) {
+                return new EphemeralDigest(in);
+            }
+
+            public EphemeralDigest[] newArray(int size) {
+                return new EphemeralDigest[size];
+            }
+        };
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 43eb751..88cf138 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8583,6 +8583,24 @@
                 "ephemeral_cookie_max_size_bytes";
 
         /**
+         * A mask applied to the ephemeral hash to generate the hash prefix.
+         * <p>
+         * Type: int
+         *
+         * @hide
+         */
+        public static final String EPHEMERAL_HASH_PREFIX_MASK = "ephemeral_hash_prefix_mask";
+
+        /**
+         * Number of hash prefixes to send during ephemeral resolution.
+         * <p>
+         * Type: int
+         *
+         * @hide
+         */
+        public static final String EPHEMERAL_HASH_PREFIX_COUNT = "ephemeral_hash_prefix_count";
+
+        /**
          * The duration for caching uninstalled ephemeral apps.
          * <p>
          * Type: long
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
index 6ba04a9..68724a7 100644
--- a/core/java/com/android/internal/app/EphemeralResolverService.java
+++ b/core/java/com/android/internal/app/EphemeralResolverService.java
@@ -39,14 +39,19 @@
 public abstract class EphemeralResolverService extends Service {
     public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
     public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+    private static final String EXTRA_PREFIX = "com.android.internal.app.PREFIX";
     private Handler mHandler;
 
     /**
      * Called to retrieve resolve info for ephemeral applications.
      *
      * @param digestPrefix The hash prefix of the ephemeral's domain.
+     * @param prefixMask A mask that was applied to each digest prefix. This should
+     *      be used when comparing against the digest prefixes as all bits might
+     *      not be set.
      */
-    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+            int digestPrefix[], int prefixMask);
 
     @Override
     protected final void attachBaseContext(Context base) {
@@ -59,10 +64,13 @@
         return new IEphemeralResolver.Stub() {
             @Override
             public void getEphemeralResolveInfoList(
-                    IRemoteCallback callback, int digestPrefix, int sequence) {
-                mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
-                        digestPrefix, sequence, callback)
-                    .sendToTarget();
+                    IRemoteCallback callback, int digestPrefix[], int prefixMask, int sequence) {
+                final Message msg = mHandler.obtainMessage(
+                        ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO, prefixMask, sequence, callback);
+                final Bundle data = new Bundle();
+                data.putIntArray(EXTRA_PREFIX, digestPrefix);
+                msg.setData(data);
+                msg.sendToTarget();
             }
         };
     }
@@ -81,8 +89,9 @@
             switch (action) {
                 case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
                     final IRemoteCallback callback = (IRemoteCallback) message.obj;
+                    final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
                     final List<EphemeralResolveInfo> resolveInfo =
-                            getEphemeralResolveInfoList(message.arg1);
+                            getEphemeralResolveInfoList(digestPrefix, message.arg1);
                     final Bundle data = new Bundle();
                     data.putInt(EXTRA_SEQUENCE, message.arg2);
                     data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl
index 40429ee..9ff1322 100644
--- a/core/java/com/android/internal/app/IEphemeralResolver.aidl
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -20,5 +20,6 @@
 import android.os.IRemoteCallback;
 
 oneway interface IEphemeralResolver {
-    void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
+    void getEphemeralResolveInfoList(IRemoteCallback callback, in int[] digestPrefix,
+            int prefixMask, int sequence);
 }