1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_MATCH_EXTERNAL;
20 
21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE;
22 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO;
23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN;
24 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_DELAY_MS;
25 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_STATUS;
26 
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.PendingIntent;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.IIntentSender;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.IntentSender;
38 import android.content.pm.ActivityInfo;
39 import android.content.pm.InstantAppRequest;
40 import android.content.pm.AuxiliaryResolveInfo;
41 import android.content.pm.InstantAppIntentFilter;
42 import android.content.pm.InstantAppResolveInfo;
43 import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
44 import android.metrics.LogMaker;
45 import android.net.Uri;
46 import android.os.Build;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.RemoteException;
50 import android.util.Log;
51 import android.util.Slog;
52 
53 import com.android.internal.logging.MetricsLogger;
54 import com.android.internal.logging.nano.MetricsProto;
55 import com.android.server.pm.InstantAppResolverConnection.ConnectionException;
56 import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback;
57 
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Collections;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Set;
66 import java.util.UUID;
67 
68 /** @hide */
69 public abstract class InstantAppResolver {
70     private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
71     private static final String TAG = "PackageManager";
72 
73     private static final int RESOLUTION_SUCCESS = 0;
74     private static final int RESOLUTION_FAILURE = 1;
75     /** Binding to the external service timed out */
76     private static final int RESOLUTION_BIND_TIMEOUT = 2;
77     /** The call to retrieve an instant application response timed out */
78     private static final int RESOLUTION_CALL_TIMEOUT = 3;
79 
80     @IntDef(flag = true, prefix = { "RESOLUTION_" }, value = {
81             RESOLUTION_SUCCESS,
82             RESOLUTION_FAILURE,
83             RESOLUTION_BIND_TIMEOUT,
84             RESOLUTION_CALL_TIMEOUT,
85     })
86     @Retention(RetentionPolicy.SOURCE)
87     public @interface ResolutionStatus {}
88 
89     private static MetricsLogger sMetricsLogger;
90 
getLogger()91     private static MetricsLogger getLogger() {
92         if (sMetricsLogger == null) {
93             sMetricsLogger = new MetricsLogger();
94         }
95         return sMetricsLogger;
96     }
97 
98     /**
99      * Returns an intent with potential PII removed from the original intent. Fields removed
100      * include extras and the host + path of the data, if defined.
101      */
sanitizeIntent(Intent origIntent)102     public static Intent sanitizeIntent(Intent origIntent) {
103         final Intent sanitizedIntent;
104         sanitizedIntent = new Intent(origIntent.getAction());
105         Set<String> categories = origIntent.getCategories();
106         if (categories != null) {
107             for (String category : categories) {
108                 sanitizedIntent.addCategory(category);
109             }
110         }
111         Uri sanitizedUri = origIntent.getData() == null
112                 ? null
113                 : Uri.fromParts(origIntent.getScheme(), "", "");
114         sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType());
115         sanitizedIntent.addFlags(origIntent.getFlags());
116         sanitizedIntent.setPackage(origIntent.getPackage());
117         return sanitizedIntent;
118     }
119 
doInstantAppResolutionPhaseOne( InstantAppResolverConnection connection, InstantAppRequest requestObj)120     public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
121             InstantAppResolverConnection connection, InstantAppRequest requestObj) {
122         final long startTime = System.currentTimeMillis();
123         final String token = UUID.randomUUID().toString();
124         if (DEBUG_INSTANT) {
125             Log.d(TAG, "[" + token + "] Phase1; resolving");
126         }
127         final Intent origIntent = requestObj.origIntent;
128         final Intent sanitizedIntent = sanitizeIntent(origIntent);
129 
130         AuxiliaryResolveInfo resolveInfo = null;
131         @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
132         try {
133             final List<InstantAppResolveInfo> instantAppResolveInfoList =
134                     connection.getInstantAppResolveInfoList(sanitizedIntent,
135                             requestObj.digest.getDigestPrefixSecure(), token);
136             if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
137                 resolveInfo = InstantAppResolver.filterInstantAppIntent(
138                         instantAppResolveInfoList, origIntent, requestObj.resolvedType,
139                         requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
140             }
141         } catch (ConnectionException e) {
142             if (e.failure == ConnectionException.FAILURE_BIND) {
143                 resolutionStatus = RESOLUTION_BIND_TIMEOUT;
144             } else if (e.failure == ConnectionException.FAILURE_CALL) {
145                 resolutionStatus = RESOLUTION_CALL_TIMEOUT;
146             } else {
147                 resolutionStatus = RESOLUTION_FAILURE;
148             }
149         }
150         // Only log successful instant application resolution
151         if (requestObj.resolveForStart && resolutionStatus == RESOLUTION_SUCCESS) {
152             logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
153                     resolutionStatus);
154         }
155         if (DEBUG_INSTANT && resolveInfo == null) {
156             if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
157                 Log.d(TAG, "[" + token + "] Phase1; bind timed out");
158             } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) {
159                 Log.d(TAG, "[" + token + "] Phase1; call timed out");
160             } else if (resolutionStatus != RESOLUTION_SUCCESS) {
161                 Log.d(TAG, "[" + token + "] Phase1; service connection error");
162             } else {
163                 Log.d(TAG, "[" + token + "] Phase1; No results matched");
164             }
165         }
166         // if the match external flag is set, return an empty resolve info instead of a null result.
167         if (resolveInfo == null && (origIntent.getFlags() & FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
168             return new AuxiliaryResolveInfo(token, false, createFailureIntent(origIntent, token),
169                     null /* filters */);
170         }
171         return resolveInfo;
172     }
173 
doInstantAppResolutionPhaseTwo(Context context, InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler)174     public static void doInstantAppResolutionPhaseTwo(Context context,
175             InstantAppResolverConnection connection, InstantAppRequest requestObj,
176             ActivityInfo instantAppInstaller, Handler callbackHandler) {
177         final long startTime = System.currentTimeMillis();
178         final String token = requestObj.responseObj.token;
179         if (DEBUG_INSTANT) {
180             Log.d(TAG, "[" + token + "] Phase2; resolving");
181         }
182         final Intent origIntent = requestObj.origIntent;
183         final Intent sanitizedIntent = sanitizeIntent(origIntent);
184 
185         final PhaseTwoCallback callback = new PhaseTwoCallback() {
186             @Override
187             void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList,
188                     long startTime) {
189                 final Intent failureIntent;
190                 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
191                     final AuxiliaryResolveInfo instantAppIntentInfo =
192                             InstantAppResolver.filterInstantAppIntent(
193                                     instantAppResolveInfoList, origIntent, null /*resolvedType*/,
194                                     0 /*userId*/, origIntent.getPackage(), requestObj.digest,
195                                     token);
196                     if (instantAppIntentInfo != null) {
197                         failureIntent = instantAppIntentInfo.failureIntent;
198                     } else {
199                         failureIntent = null;
200                     }
201                 } else {
202                     failureIntent = null;
203                 }
204                 final Intent installerIntent = buildEphemeralInstallerIntent(
205                         requestObj.origIntent,
206                         sanitizedIntent,
207                         failureIntent,
208                         requestObj.callingPackage,
209                         requestObj.verificationBundle,
210                         requestObj.resolvedType,
211                         requestObj.userId,
212                         requestObj.responseObj.installFailureActivity,
213                         token,
214                         false /*needsPhaseTwo*/,
215                         requestObj.responseObj.filters);
216                 installerIntent.setComponent(new ComponentName(
217                         instantAppInstaller.packageName, instantAppInstaller.name));
218 
219                 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
220                         requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
221 
222                 context.startActivity(installerIntent);
223             }
224         };
225         try {
226             connection.getInstantAppIntentFilterList(sanitizedIntent,
227                     requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler,
228                     startTime);
229         } catch (ConnectionException e) {
230             @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
231             if (e.failure == ConnectionException.FAILURE_BIND) {
232                 resolutionStatus = RESOLUTION_BIND_TIMEOUT;
233             }
234             logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
235                     resolutionStatus);
236             if (DEBUG_INSTANT) {
237                 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
238                     Log.d(TAG, "[" + token + "] Phase2; bind timed out");
239                 } else {
240                     Log.d(TAG, "[" + token + "] Phase2; service connection error");
241                 }
242             }
243         }
244     }
245 
246     /**
247      * Builds and returns an intent to launch the instant installer.
248      */
buildEphemeralInstallerIntent( @onNull Intent origIntent, @NonNull Intent sanitizedIntent, @Nullable Intent failureIntent, @NonNull String callingPackage, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, @Nullable ComponentName installFailureActivity, @Nullable String token, boolean needsPhaseTwo, List<AuxiliaryResolveInfo.AuxiliaryFilter> filters)249     public static Intent buildEphemeralInstallerIntent(
250             @NonNull Intent origIntent,
251             @NonNull Intent sanitizedIntent,
252             @Nullable Intent failureIntent,
253             @NonNull String callingPackage,
254             @Nullable Bundle verificationBundle,
255             @NonNull String resolvedType,
256             int userId,
257             @Nullable ComponentName installFailureActivity,
258             @Nullable String token,
259             boolean needsPhaseTwo,
260             List<AuxiliaryResolveInfo.AuxiliaryFilter> filters) {
261         // Construct the intent that launches the instant installer
262         int flags = origIntent.getFlags();
263         final Intent intent = new Intent();
264         intent.setFlags(flags
265                 | Intent.FLAG_ACTIVITY_NO_HISTORY
266                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
267         if (token != null) {
268             // TODO(b/72700831): remove populating old extra
269             intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token);
270             intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token);
271         }
272         if (origIntent.getData() != null) {
273             // TODO(b/72700831): remove populating old extra
274             intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
275             intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost());
276         }
277         intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
278         intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
279 
280         if (needsPhaseTwo) {
281             intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE);
282         } else {
283             // We have all of the data we need; just start the installer without a second phase
284             if (failureIntent != null || installFailureActivity != null) {
285                 // Intent that is launched if the package couldn't be installed for any reason.
286                 try {
287                     final Intent onFailureIntent;
288                     if (installFailureActivity != null) {
289                         onFailureIntent = new Intent();
290                         onFailureIntent.setComponent(installFailureActivity);
291                         if (filters != null && filters.size() == 1) {
292                             onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
293                                     filters.get(0).splitName);
294                         }
295                         onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
296                     } else {
297                         onFailureIntent = failureIntent;
298                     }
299                     final IIntentSender failureIntentTarget = ActivityManager.getService()
300                             .getIntentSender(
301                                     ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
302                                     null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
303                                     new Intent[] { onFailureIntent },
304                                     new String[] { resolvedType },
305                                     PendingIntent.FLAG_CANCEL_CURRENT
306                                             | PendingIntent.FLAG_ONE_SHOT
307                                             | PendingIntent.FLAG_IMMUTABLE,
308                                     null /*bOptions*/, userId);
309                     IntentSender failureSender = new IntentSender(failureIntentTarget);
310                     // TODO(b/72700831): remove populating old extra
311                     intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, failureSender);
312                     intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender);
313                 } catch (RemoteException ignore) { /* ignore; same process */ }
314             }
315 
316             // Intent that is launched if the package was installed successfully.
317             final Intent successIntent = new Intent(origIntent);
318             successIntent.setLaunchToken(token);
319             try {
320                 final IIntentSender successIntentTarget = ActivityManager.getService()
321                         .getIntentSender(
322                                 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
323                                 null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
324                                 new Intent[] { successIntent },
325                                 new String[] { resolvedType },
326                                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
327                                         | PendingIntent.FLAG_IMMUTABLE,
328                                 null /*bOptions*/, userId);
329                 IntentSender successSender = new IntentSender(successIntentTarget);
330                 // TODO(b/72700831): remove populating old extra
331                 intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, successSender);
332                 intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender);
333             } catch (RemoteException ignore) { /* ignore; same process */ }
334             if (verificationBundle != null) {
335                 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
336             }
337             intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
338 
339             if (filters != null) {
340                 Bundle resolvableFilters[] = new Bundle[filters.size()];
341                 for (int i = 0, max = filters.size(); i < max; i++) {
342                     Bundle resolvableFilter = new Bundle();
343                     AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i);
344                     resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP,
345                             filter.resolveInfo != null
346                                     && filter.resolveInfo.shouldLetInstallerDecide());
347                     resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName);
348                     resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName);
349                     resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode);
350                     resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras);
351                     resolvableFilters[i] = resolvableFilter;
352                     if (i == 0) {
353                         // for backwards compat, always set the first result on the intent and add
354                         // the int version code
355                         intent.putExtras(resolvableFilter);
356                         intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode);
357                     }
358                 }
359                 intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters);
360             }
361             intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
362         }
363         return intent;
364     }
365 
filterInstantAppIntent( List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent, String resolvedType, int userId, String packageName, InstantAppDigest digest, String token)366     private static AuxiliaryResolveInfo filterInstantAppIntent(
367             List<InstantAppResolveInfo> instantAppResolveInfoList,
368             Intent origIntent, String resolvedType, int userId, String packageName,
369             InstantAppDigest digest, String token) {
370         final int[] shaPrefix = digest.getDigestPrefix();
371         final byte[][] digestBytes = digest.getDigestBytes();
372         boolean requiresSecondPhase = false;
373         ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
374         boolean requiresPrefixMatch = origIntent.isWebIntent() || (shaPrefix.length > 0
375                         && (origIntent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0);
376         for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
377             if (requiresPrefixMatch && instantAppResolveInfo.shouldLetInstallerDecide()) {
378                 Slog.d(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
379                         + " required; ignoring");
380                 continue;
381             }
382             byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
383             // Only include matching digests if we have a prefix and we're either dealing with a
384             // prefixed request or the resolveInfo specifies digest details.
385             if (shaPrefix.length > 0 && (requiresPrefixMatch || filterDigestBytes.length > 0)) {
386                 boolean matchFound = false;
387                 // Go in reverse order so we match the narrowest scope first.
388                 for (int i = shaPrefix.length - 1; i >= 0; --i) {
389                     if (Arrays.equals(digestBytes[i], filterDigestBytes)) {
390                         matchFound = true;
391                         break;
392                     }
393                 }
394                 if (!matchFound) {
395                     continue;
396                 }
397             }
398             // We matched a resolve info; resolve the filters to see if anything matches completely.
399             List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
400                     origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
401             if (matchFilters != null) {
402                 if (matchFilters.isEmpty()) {
403                     requiresSecondPhase = true;
404                 }
405                 if (filters == null) {
406                     filters = new ArrayList<>(matchFilters);
407                 } else {
408                     filters.addAll(matchFilters);
409                 }
410             }
411         }
412         if (filters != null && !filters.isEmpty()) {
413             return new AuxiliaryResolveInfo(token, requiresSecondPhase,
414                     createFailureIntent(origIntent, token), filters);
415         }
416         // Hash or filter mis-match; no instant apps for this domain.
417         return null;
418     }
419 
420     /**
421      * Creates a failure intent for the installer to send in the case that the instant app cannot be
422      * launched for any reason.
423      */
createFailureIntent(Intent origIntent, String token)424     private static Intent createFailureIntent(Intent origIntent, String token) {
425         final Intent failureIntent = new Intent(origIntent);
426         failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
427         failureIntent.setFlags(failureIntent.getFlags() & ~Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
428         failureIntent.setLaunchToken(token);
429         return failureIntent;
430     }
431 
432     /**
433      * Returns one of three states: <p/>
434      * <ul>
435      *     <li>{@code null} if there are no matches will not be; resolution is unnecessary.</li>
436      *     <li>An empty list signifying that a 2nd phase of resolution is required.</li>
437      *     <li>A populated list meaning that matches were found and should be sent directly to the
438      *     installer</li>
439      * </ul>
440      *
441      */
computeResolveFilters( Intent origIntent, String resolvedType, int userId, String packageName, String token, InstantAppResolveInfo instantAppInfo)442     private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
443             Intent origIntent, String resolvedType, int userId, String packageName, String token,
444             InstantAppResolveInfo instantAppInfo) {
445         if (instantAppInfo.shouldLetInstallerDecide()) {
446             return Collections.singletonList(
447                     new AuxiliaryResolveInfo.AuxiliaryFilter(
448                             instantAppInfo, null /* splitName */,
449                             instantAppInfo.getExtras()));
450         }
451         if (packageName != null
452                 && !packageName.equals(instantAppInfo.getPackageName())) {
453             return null;
454         }
455         final List<InstantAppIntentFilter> instantAppFilters =
456                 instantAppInfo.getIntentFilters();
457         if (instantAppFilters == null || instantAppFilters.isEmpty()) {
458             // No filters on web intent; no matches, 2nd phase unnecessary.
459             if (origIntent.isWebIntent()) {
460                 return null;
461             }
462             // No filters; we need to start phase two
463             if (DEBUG_INSTANT) {
464                 Log.d(TAG, "No app filters; go to phase 2");
465             }
466             return Collections.emptyList();
467         }
468         final PackageManagerService.InstantAppIntentResolver instantAppResolver =
469                 new PackageManagerService.InstantAppIntentResolver();
470         for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
471             final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
472             final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
473             if (splitFilters == null || splitFilters.isEmpty()) {
474                 continue;
475             }
476             for (int k = splitFilters.size() - 1; k >= 0; --k) {
477                 IntentFilter filter = splitFilters.get(k);
478                 Iterator<IntentFilter.AuthorityEntry> authorities =
479                         filter.authoritiesIterator();
480                 // ignore http/s-only filters.
481                 if ((authorities == null || !authorities.hasNext())
482                         && (filter.hasDataScheme("http") || filter.hasDataScheme("https"))
483                         && filter.hasAction(Intent.ACTION_VIEW)
484                         && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
485                     continue;
486                 }
487                 instantAppResolver.addFilter(
488                         new AuxiliaryResolveInfo.AuxiliaryFilter(
489                                 filter,
490                                 instantAppInfo,
491                                 instantAppFilter.getSplitName(),
492                                 instantAppInfo.getExtras()
493                         ));
494             }
495         }
496         List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
497                 instantAppResolver.queryIntent(
498                         origIntent, resolvedType, false /*defaultOnly*/, userId);
499         if (!matchedResolveInfoList.isEmpty()) {
500             if (DEBUG_INSTANT) {
501                 Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
502             }
503             return matchedResolveInfoList;
504         } else if (DEBUG_INSTANT) {
505             Log.d(TAG, "[" + token + "] No matches found"
506                     + " package: " + instantAppInfo.getPackageName()
507                     + ", versionCode: " + instantAppInfo.getVersionCode());
508         }
509         return null;
510     }
511 
logMetrics(int action, long startTime, String token, @ResolutionStatus int status)512     private static void logMetrics(int action, long startTime, String token,
513             @ResolutionStatus int status) {
514         final LogMaker logMaker = new LogMaker(action)
515                 .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
516                 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS,
517                         new Long(System.currentTimeMillis() - startTime))
518                 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token)
519                 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status));
520         getLogger().write(logMaker);
521     }
522 }
523