Revert "Revert "Adds generic intent Instant App resolution""

This reverts commit 860b8ba71938e9860a31881c1d1431877f9d01a2.

The original change that was reverted contained a bug that allowed an
http view/browsable intent used to query for browsers to be considered
as a candidate for instant apps. This was resulting in an attempt to
bind to the instant app resolver while holding a lock on mPackages.

This change ensures that PMS doesn't bind while checking for the browser
status of a package in both the instant app filtering code and by adding
the FLAG_IGNORE_EPHEMERAL to the canonical browser intent.

Reason for revert: Applying fix

Change-Id: I4896b3a15416a11fdc3f6c191e552c4ce8963623
Fixes: 63117034
Fixes: 71916178
Test: Manual using test app at google_experimental/users/patb/InstantAppsInP
Test: atest android.appsecurity.cts.EphemeralTest passes after modification
diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java
index 427a0386..d1c2441 100644
--- a/core/java/android/app/EphemeralResolverService.java
+++ b/core/java/android/app/EphemeralResolverService.java
@@ -17,20 +17,10 @@
 package android.app;
 
 import android.annotation.SystemApi;
-import android.app.Service;
-import android.app.InstantAppResolverService.InstantAppResolutionCallback;
-import android.content.Context;
-import android.content.Intent;
 import android.content.pm.EphemeralResolveInfo;
 import android.content.pm.InstantAppResolveInfo;
 import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
 import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -85,7 +75,6 @@
         return super.getLooper();
     }
 
-    @Override
     void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
             InstantAppResolutionCallback callback) {
         if (DEBUG_EPHEMERAL) {
@@ -101,7 +90,6 @@
         callback.onInstantAppResolveInfo(resultList);
     }
 
-    @Override
     void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
             String hostName, InstantAppResolutionCallback callback) {
         if (DEBUG_EPHEMERAL) {
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 805d8c0..ae20057 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -16,13 +16,15 @@
 
 package android.app;
 
+import android.content.Intent;
 import android.os.IRemoteCallback;
 
 /** @hide */
 oneway interface IInstantAppResolver {
-    void getInstantAppResolveInfoList(in int[] digestPrefix,
+    void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
             String token, int sequence, IRemoteCallback callback);
 
-    void getInstantAppIntentFilterList(in int[] digestPrefix,
-            String token, String hostName, IRemoteCallback callback);
+    void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
+            String token, IRemoteCallback callback);
+
 }
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index c5dc86c..89aff36 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -17,7 +17,6 @@
 package android.app;
 
 import android.annotation.SystemApi;
-import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.InstantAppResolveInfo;
@@ -35,6 +34,7 @@
 import com.android.internal.os.SomeArgs;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -53,23 +53,65 @@
     Handler mHandler;
 
     /**
-     * Called to retrieve resolve info for instant applications.
+     * Called to retrieve resolve info for instant applications immediately.
      *
      * @param digestPrefix The hash prefix of the instant app's domain.
+     * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String,
+     *             InstantAppResolutionCallback)}
      */
+    @Deprecated
     public void onGetInstantAppResolveInfo(
             int digestPrefix[], String token, InstantAppResolutionCallback callback) {
         throw new IllegalStateException("Must define");
     }
 
     /**
-     * Called to retrieve intent filters for instant applications.
+     * Called to retrieve intent filters for instant applications from potentially expensive
+     * sources.
      *
      * @param digestPrefix The hash prefix of the instant app's domain.
+     * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String,
+     *             InstantAppResolutionCallback)}
      */
+    @Deprecated
     public void onGetInstantAppIntentFilter(
             int digestPrefix[], String token, InstantAppResolutionCallback callback) {
-        throw new IllegalStateException("Must define");
+        throw new IllegalStateException("Must define onGetInstantAppIntentFilter");
+    }
+
+    /**
+     * Called to retrieve resolve info for instant applications immediately.
+     *
+     * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
+     * @param hostDigestPrefix The hash prefix of the instant app's domain.
+     */
+    public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix,
+            String token, InstantAppResolutionCallback callback) {
+        // if not overridden, forward to old methods and filter out non-web intents
+        if (sanitizedIntent.isBrowsableWebIntent()) {
+            onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
+        } else {
+            callback.onInstantAppResolveInfo(Collections.emptyList());
+        }
+    }
+
+    /**
+     * Called to retrieve intent filters for instant applications from potentially expensive
+     * sources.
+     *
+     * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
+     * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
+     *                         defined.
+     */
+    public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix,
+            String token, InstantAppResolutionCallback callback) {
+        Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
+        // if not overridden, forward to old methods and filter out non-web intents
+        if (sanitizedIntent.isBrowsableWebIntent()) {
+            onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
+        } else {
+            callback.onInstantAppResolveInfo(Collections.emptyList());
+        }
     }
 
     /**
@@ -89,8 +131,8 @@
     public final IBinder onBind(Intent intent) {
         return new IInstantAppResolver.Stub() {
             @Override
-            public void getInstantAppResolveInfoList(
-                    int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
+            public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
+                    String token, int sequence, IRemoteCallback callback) {
                 if (DEBUG_EPHEMERAL) {
                     Slog.v(TAG, "[" + token + "] Phase1 called; posting");
                 }
@@ -98,14 +140,14 @@
                 args.arg1 = callback;
                 args.arg2 = digestPrefix;
                 args.arg3 = token;
-                mHandler.obtainMessage(
-                                ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
-                        .sendToTarget();
+                args.arg4 = sanitizedIntent;
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
+                        sequence, 0, args).sendToTarget();
             }
 
             @Override
-            public void getInstantAppIntentFilterList(
-                    int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
+            public void getInstantAppIntentFilterList(Intent sanitizedIntent,
+                    int[] digestPrefix, String token, IRemoteCallback callback) {
                 if (DEBUG_EPHEMERAL) {
                     Slog.v(TAG, "[" + token + "] Phase2 called; posting");
                 }
@@ -113,9 +155,9 @@
                 args.arg1 = callback;
                 args.arg2 = digestPrefix;
                 args.arg3 = token;
-                args.arg4 = hostName;
-                mHandler.obtainMessage(
-                        ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
+                args.arg4 = sanitizedIntent;
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
+                        callback).sendToTarget();
             }
         };
     }
@@ -142,29 +184,9 @@
         }
     }
 
-    @Deprecated
-    void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
-            InstantAppResolutionCallback callback) {
-        if (DEBUG_EPHEMERAL) {
-            Slog.d(TAG, "[" + token + "] Phase1 request;"
-                    + " prefix: " + Arrays.toString(digestPrefix));
-        }
-        onGetInstantAppResolveInfo(digestPrefix, token, callback);
-    }
-    @Deprecated
-    void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
-            InstantAppResolutionCallback callback) {
-        if (DEBUG_EPHEMERAL) {
-            Slog.d(TAG, "[" + token + "] Phase2 request;"
-                    + " prefix: " + Arrays.toString(digestPrefix));
-        }
-        onGetInstantAppIntentFilter(digestPrefix, token, callback);
-    }
-
     private final class ServiceHandler extends Handler {
         public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1;
         public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2;
-
         public ServiceHandler(Looper looper) {
             super(looper, null /*callback*/, true /*async*/);
         }
@@ -179,9 +201,13 @@
                     final IRemoteCallback callback = (IRemoteCallback) args.arg1;
                     final int[] digestPrefix = (int[]) args.arg2;
                     final String token = (String) args.arg3;
+                    final Intent intent = (Intent) args.arg4;
                     final int sequence = message.arg1;
-                    _onGetInstantAppResolveInfo(
-                            digestPrefix, token,
+                    if (DEBUG_EPHEMERAL) {
+                        Slog.d(TAG, "[" + token + "] Phase1 request;"
+                                + " prefix: " + Arrays.toString(digestPrefix));
+                    }
+                    onGetInstantAppResolveInfo(intent, digestPrefix, token,
                             new InstantAppResolutionCallback(sequence, callback));
                 } break;
 
@@ -190,9 +216,12 @@
                     final IRemoteCallback callback = (IRemoteCallback) args.arg1;
                     final int[] digestPrefix = (int[]) args.arg2;
                     final String token = (String) args.arg3;
-                    final String hostName = (String) args.arg4;
-                    _onGetInstantAppIntentFilter(
-                            digestPrefix, token, hostName,
+                    final Intent intent = (Intent) args.arg4;
+                    if (DEBUG_EPHEMERAL) {
+                        Slog.d(TAG, "[" + token + "] Phase2 request;"
+                                + " prefix: " + Arrays.toString(digestPrefix));
+                    }
+                    onGetInstantAppIntentFilter(intent, digestPrefix, token,
                             new InstantAppResolutionCallback(-1 /*sequence*/, callback));
                 } break;
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e02a294..b3c8737 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -50,6 +50,7 @@
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -4472,7 +4473,15 @@
     public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
 
     /**
-     * A {@link Bundle} of metadata that describes the instanta application that needs to be
+     * An array of {@link Bundle}s containing details about resolved instant apps..
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_INSTANT_APP_BUNDLES =
+            "android.intent.extra.INSTANT_APP_BUNDLES";
+
+    /**
+     * A {@link Bundle} of metadata that describes the instant application that needs to be
      * installed. This data is populated from the response to
      * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered
      * instant application resolver.
@@ -4482,6 +4491,16 @@
             "android.intent.extra.INSTANT_APP_EXTRAS";
 
     /**
+     * A boolean value indicating that the instant app resolver was unable to state with certainty
+     * that it did or did not have an app for the sanitized {@link Intent} defined at
+     * {@link #EXTRA_INTENT}.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_UNKNOWN_INSTANT_APP =
+            "android.intent.extra.UNKNOWN_INSTANT_APP";
+
+    /**
      * The version code of the app to install components from.
      * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE).
      * @hide
@@ -5029,6 +5048,7 @@
             FLAG_GRANT_PREFIX_URI_PERMISSION,
             FLAG_DEBUG_TRIAGED_MISSING,
             FLAG_IGNORE_EPHEMERAL,
+            FLAG_ACTIVITY_MATCH_EXTERNAL,
             FLAG_ACTIVITY_NO_HISTORY,
             FLAG_ACTIVITY_SINGLE_TOP,
             FLAG_ACTIVITY_NEW_TASK,
@@ -5072,6 +5092,7 @@
             FLAG_INCLUDE_STOPPED_PACKAGES,
             FLAG_DEBUG_TRIAGED_MISSING,
             FLAG_IGNORE_EPHEMERAL,
+            FLAG_ACTIVITY_MATCH_EXTERNAL,
             FLAG_ACTIVITY_NO_HISTORY,
             FLAG_ACTIVITY_SINGLE_TOP,
             FLAG_ACTIVITY_NEW_TASK,
@@ -5475,6 +5496,14 @@
      */
     public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000;
 
+
+    /**
+     * If set, resolution of this intent may take place via an instant app not
+     * yet on the device if there does not yet exist an app on device to
+     * resolve it.
+     */
+    public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800;
+
     /**
      * If set, when sending a broadcast only registered receivers will be
      * called -- no BroadcastReceiver components will be launched.
@@ -10028,6 +10057,25 @@
         }
     }
 
+    /** @hide */
+    public boolean hasWebURI() {
+        if (getData() == null) {
+            return false;
+        }
+        final String scheme = getScheme();
+        if (TextUtils.isEmpty(scheme)) {
+            return false;
+        }
+        return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
+    }
+
+    /** @hide */
+    public boolean isBrowsableWebIntent() {
+        return ACTION_VIEW.equals(mAction)
+                && hasCategory(CATEGORY_BROWSABLE)
+                && hasWebURI();
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java
index 6bdcefb..202df50 100644
--- a/core/java/android/content/pm/AuxiliaryResolveInfo.java
+++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java
@@ -21,6 +21,10 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Bundle;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Auxiliary application resolution response.
@@ -31,56 +35,95 @@
  * hasn't been installed.
  * @hide
  */
-public final class AuxiliaryResolveInfo extends IntentFilter {
-    /** Resolved information returned from the external instant resolver */
-    public final InstantAppResolveInfo resolveInfo;
-    /** The resolved package. Copied from {@link #resolveInfo}. */
-    public final String packageName;
+public final class AuxiliaryResolveInfo {
     /** The activity to launch if there's an installation failure. */
     public final ComponentName installFailureActivity;
-    /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
-    public final String splitName;
     /** Whether or not instant resolution needs the second phase */
     public final boolean needsPhaseTwo;
     /** Opaque token to track the instant application resolution */
     public final String token;
-    /** The version code of the package */
-    public final long versionCode;
     /** An intent to start upon failure to install */
     public final Intent failureIntent;
+    /** The matching filters for this resolve info. */
+    public final List<AuxiliaryFilter> filters;
 
     /** Create a response for installing an instant application. */
-    public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo,
-            @NonNull IntentFilter orig,
-            @Nullable String splitName,
-            @NonNull String token,
+    public AuxiliaryResolveInfo(@NonNull String token,
             boolean needsPhase2,
-            @Nullable Intent failureIntent) {
-        super(orig);
-        this.resolveInfo = resolveInfo;
-        this.packageName = resolveInfo.getPackageName();
-        this.splitName = splitName;
+            @Nullable Intent failureIntent,
+            @Nullable List<AuxiliaryFilter> filters) {
         this.token = token;
         this.needsPhaseTwo = needsPhase2;
-        this.versionCode = resolveInfo.getVersionCode();
         this.failureIntent = failureIntent;
+        this.filters = filters;
         this.installFailureActivity = null;
     }
 
     /** Create a response for installing a split on demand. */
-    public AuxiliaryResolveInfo(@NonNull String packageName,
-            @Nullable String splitName,
-            @Nullable ComponentName failureActivity,
-            long versionCode,
-            @Nullable Intent failureIntent) {
+    public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity,
+            @Nullable Intent failureIntent,
+            @Nullable List<AuxiliaryFilter> filters) {
         super();
-        this.packageName = packageName;
         this.installFailureActivity = failureActivity;
-        this.splitName = splitName;
-        this.versionCode = versionCode;
-        this.resolveInfo = null;
+        this.filters = filters;
         this.token = null;
         this.needsPhaseTwo = false;
         this.failureIntent = failureIntent;
     }
+
+    /** Create a response for installing a split on demand. */
+    public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity,
+            String packageName, long versionCode, String splitName) {
+        this(failureActivity, null, Collections.singletonList(
+                new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName)));
+    }
+
+    /** @hide */
+    public static final class AuxiliaryFilter extends IntentFilter {
+        /** Resolved information returned from the external instant resolver */
+        public final InstantAppResolveInfo resolveInfo;
+        /** The resolved package. Copied from {@link #resolveInfo}. */
+        public final String packageName;
+        /** The version code of the package */
+        public final long versionCode;
+        /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
+        public final String splitName;
+        /** The extras to pass on to the installer for this filter. */
+        public final Bundle extras;
+
+        public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo,
+                String splitName, Bundle extras) {
+            super(orig);
+            this.resolveInfo = resolveInfo;
+            this.packageName = resolveInfo.getPackageName();
+            this.versionCode = resolveInfo.getLongVersionCode();
+            this.splitName = splitName;
+            this.extras = extras;
+        }
+
+        public AuxiliaryFilter(InstantAppResolveInfo resolveInfo,
+                String splitName, Bundle extras) {
+            this.resolveInfo = resolveInfo;
+            this.packageName = resolveInfo.getPackageName();
+            this.versionCode = resolveInfo.getLongVersionCode();
+            this.splitName = splitName;
+            this.extras = extras;
+        }
+
+        public AuxiliaryFilter(String packageName, long versionCode, String splitName) {
+            this.resolveInfo = null;
+            this.packageName = packageName;
+            this.versionCode = versionCode;
+            this.splitName = splitName;
+            this.extras = null;
+        }
+
+        @Override
+        public String toString() {
+            return "AuxiliaryFilter{"
+                    + "packageName='" + packageName + '\''
+                    + ", versionCode=" + versionCode
+                    + ", splitName='" + splitName + '\'' + '}';
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 19cb932..112c5da 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -26,11 +27,35 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 
 /**
- * Information about an instant application.
+ * Describes an externally resolvable instant application. There are three states that this class
+ * can represent: <p/>
+ * <ul>
+ *     <li>
+ *         The first, usable only for non http/s intents, implies that the resolver cannot
+ *         immediately resolve this intent and would prefer that resolution be deferred to the
+ *         instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}.
+ *         If the {@link android.content.Intent} has the scheme set to http/s and a set of digest
+ *         prefixes were passed into one of the resolve methods in
+ *         {@link android.app.InstantAppResolverService}, this state cannot be used.
+ *     </li>
+ *     <li>
+ *         The second represents a partial match and is constructed with any of the other
+ *         constructors. By setting one or more of the {@link Nullable}arguments to null, you
+ *         communicate to the resolver in response to
+ *         {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[],
+ *                String, InstantAppResolverService.InstantAppResolutionCallback)}
+ *         that you need a 2nd round of resolution to complete the request.
+ *     </li>
+ *     <li>
+ *         The third represents a complete match and is constructed with all @Nullable parameters
+ *         populated.
+ *     </li>
+ * </ul>
  * @hide
  */
 @SystemApi
@@ -38,6 +63,8 @@
     /** Algorithm that will be used to generate the domain digest */
     private static final String SHA_ALGORITHM = "SHA-256";
 
+    private static final byte[] EMPTY_DIGEST = new byte[0];
+
     private final InstantAppDigest mDigest;
     private final String mPackageName;
     /** The filters used to match domain */
@@ -46,15 +73,30 @@
     private final long mVersionCode;
     /** Data about the app that should be passed along to the Instant App installer on resolve */
     private final Bundle mExtras;
+    /**
+     * A flag that indicates that the resolver is aware that an app may match, but would prefer
+     * that the installer get the sanitized intent to decide. This should not be used for
+     * resolutions that include a host and will be ignored in such cases.
+     */
+    private final boolean mShouldLetInstallerDecide;
 
+    /** Constructor for intent-based InstantApp resolution results. */
     public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
             @Nullable List<InstantAppIntentFilter> filters, int versionCode) {
         this(digest, packageName, filters, (long) versionCode, null /* extras */);
     }
 
+    /** Constructor for intent-based InstantApp resolution results with extras. */
     public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
             @Nullable List<InstantAppIntentFilter> filters, long versionCode,
             @Nullable Bundle extras) {
+        this(digest, packageName, filters, versionCode, extras, false);
+    }
+
+    /** Constructor for intent-based InstantApp resolution results with extras. */
+    private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
+            @Nullable List<InstantAppIntentFilter> filters, long versionCode,
+            @Nullable Bundle extras, boolean shouldLetInstallerDecide) {
         // validate arguments
         if ((packageName == null && (filters != null && filters.size() != 0))
                 || (packageName != null && (filters == null || filters.size() == 0))) {
@@ -62,7 +104,7 @@
         }
         mDigest = digest;
         if (filters != null) {
-            mFilters = new ArrayList<InstantAppIntentFilter>(filters.size());
+            mFilters = new ArrayList<>(filters.size());
             mFilters.addAll(filters);
         } else {
             mFilters = null;
@@ -70,25 +112,48 @@
         mPackageName = packageName;
         mVersionCode = versionCode;
         mExtras = extras;
+        mShouldLetInstallerDecide = shouldLetInstallerDecide;
     }
 
+    /** Constructor for intent-based InstantApp resolution results by hostname. */
     public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName,
             @Nullable List<InstantAppIntentFilter> filters) {
         this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/,
                 null /* extras */);
     }
 
+    /**
+     * Constructor that creates a "let the installer decide" response with optional included
+     * extras.
+     */
+    public InstantAppResolveInfo(@Nullable Bundle extras) {
+        this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true);
+    }
+
     InstantAppResolveInfo(Parcel in) {
-        mDigest = in.readParcelable(null /*loader*/);
-        mPackageName = in.readString();
-        mFilters = new ArrayList<InstantAppIntentFilter>();
-        in.readList(mFilters, null /*loader*/);
-        mVersionCode = in.readLong();
+        mShouldLetInstallerDecide = in.readBoolean();
         mExtras = in.readBundle();
+        if (mShouldLetInstallerDecide) {
+            mDigest = InstantAppDigest.UNDEFINED;
+            mPackageName = null;
+            mFilters = Collections.emptyList();
+            mVersionCode = -1;
+        } else {
+            mDigest = in.readParcelable(null /*loader*/);
+            mPackageName = in.readString();
+            mFilters = new ArrayList<>();
+            in.readList(mFilters, null /*loader*/);
+            mVersionCode = in.readLong();
+        }
+    }
+
+    /** Returns true if the installer should be notified that it should query for packages. */
+    public boolean shouldLetInstallerDecide() {
+        return mShouldLetInstallerDecide;
     }
 
     public byte[] getDigestBytes() {
-        return mDigest.getDigestBytes()[0];
+        return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST;
     }
 
     public int getDigestPrefix() {
@@ -127,11 +192,15 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        out.writeBoolean(mShouldLetInstallerDecide);
+        out.writeBundle(mExtras);
+        if (mShouldLetInstallerDecide) {
+            return;
+        }
         out.writeParcelable(mDigest, flags);
         out.writeString(mPackageName);
         out.writeList(mFilters);
         out.writeLong(mVersionCode);
-        out.writeBundle(mExtras);
     }
 
     public static final Parcelable.Creator<InstantAppResolveInfo> CREATOR
@@ -159,7 +228,9 @@
     @SystemApi
     public static final class InstantAppDigest implements Parcelable {
         private static final int DIGEST_MASK = 0xfffff000;
-        private static final int DIGEST_PREFIX_COUNT = 5;
+
+        public static final InstantAppDigest UNDEFINED =
+                new InstantAppDigest(new byte[][]{}, new int[]{});
         /** Full digest of the domain hashes */
         private final byte[][] mDigestBytes;
         /** The first 4 bytes of the domain hashes */
@@ -186,6 +257,11 @@
             }
         }
 
+        private InstantAppDigest(byte[][] digestBytes, int[] prefix) {
+            this.mDigestPrefix = prefix;
+            this.mDigestBytes = digestBytes;
+        }
+
         private static byte[][] generateDigest(String hostName, int maxDigests) {
             ArrayList<byte[]> digests = new ArrayList<>();
             try {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 2ab2d20..1dfff5e 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -103,11 +103,14 @@
         List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
         for (int i = 0, N = intents.size(); i < N; i++) {
             final Intent intent = intents.get(i);
-            final List<ResolveInfo> infos = mpm.queryIntentActivities(intent,
-                    PackageManager.MATCH_DEFAULT_ONLY
-                            | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
-                            | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)
-                            | PackageManager.MATCH_INSTANT);
+            int flags = PackageManager.MATCH_DEFAULT_ONLY
+                    | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
+                    | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
+            if (intent.isBrowsableWebIntent()
+                        || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
+                flags |= PackageManager.MATCH_INSTANT;
+            }
+            final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
             // Remove any activities that are not exported.
             int totalSize = infos.size();
             for (int j = totalSize - 1; j >= 0 ; j--) {