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.AuxiliaryResolveInfo;
40 import android.content.pm.InstantAppIntentFilter;
41 import android.content.pm.InstantAppRequest;
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(), requestObj.userId, 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(), requestObj.userId, token, callback,
228                     callbackHandler, 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             intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token);
269         }
270         if (origIntent.getData() != null) {
271             intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost());
272         }
273         intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
274         intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
275 
276         if (needsPhaseTwo) {
277             intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE);
278         } else {
279             // We have all of the data we need; just start the installer without a second phase
280             if (failureIntent != null || installFailureActivity != null) {
281                 // Intent that is launched if the package couldn't be installed for any reason.
282                 try {
283                     final Intent onFailureIntent;
284                     if (installFailureActivity != null) {
285                         onFailureIntent = new Intent();
286                         onFailureIntent.setComponent(installFailureActivity);
287                         if (filters != null && filters.size() == 1) {
288                             onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
289                                     filters.get(0).splitName);
290                         }
291                         onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
292                     } else {
293                         onFailureIntent = failureIntent;
294                     }
295                     final IIntentSender failureIntentTarget = ActivityManager.getService()
296                             .getIntentSender(
297                                     ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
298                                     null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
299                                     new Intent[] { onFailureIntent },
300                                     new String[] { resolvedType },
301                                     PendingIntent.FLAG_CANCEL_CURRENT
302                                             | PendingIntent.FLAG_ONE_SHOT
303                                             | PendingIntent.FLAG_IMMUTABLE,
304                                     null /*bOptions*/, userId);
305                     IntentSender failureSender = new IntentSender(failureIntentTarget);
306                     // TODO(b/72700831): remove populating old extra
307                     intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender);
308                 } catch (RemoteException ignore) { /* ignore; same process */ }
309             }
310 
311             // Intent that is launched if the package was installed successfully.
312             final Intent successIntent = new Intent(origIntent);
313             successIntent.setLaunchToken(token);
314             try {
315                 final IIntentSender successIntentTarget = ActivityManager.getService()
316                         .getIntentSender(
317                                 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
318                                 null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
319                                 new Intent[] { successIntent },
320                                 new String[] { resolvedType },
321                                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
322                                         | PendingIntent.FLAG_IMMUTABLE,
323                                 null /*bOptions*/, userId);
324                 IntentSender successSender = new IntentSender(successIntentTarget);
325                 intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender);
326             } catch (RemoteException ignore) { /* ignore; same process */ }
327             if (verificationBundle != null) {
328                 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
329             }
330             intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
331 
332             if (filters != null) {
333                 Bundle resolvableFilters[] = new Bundle[filters.size()];
334                 for (int i = 0, max = filters.size(); i < max; i++) {
335                     Bundle resolvableFilter = new Bundle();
336                     AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i);
337                     resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP,
338                             filter.resolveInfo != null
339                                     && filter.resolveInfo.shouldLetInstallerDecide());
340                     resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName);
341                     resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName);
342                     resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode);
343                     resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras);
344                     resolvableFilters[i] = resolvableFilter;
345                     if (i == 0) {
346                         // for backwards compat, always set the first result on the intent and add
347                         // the int version code
348                         intent.putExtras(resolvableFilter);
349                         intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode);
350                     }
351                 }
352                 intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters);
353             }
354             intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
355         }
356         return intent;
357     }
358 
filterInstantAppIntent( List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent, String resolvedType, int userId, String packageName, InstantAppDigest digest, String token)359     private static AuxiliaryResolveInfo filterInstantAppIntent(
360             List<InstantAppResolveInfo> instantAppResolveInfoList,
361             Intent origIntent, String resolvedType, int userId, String packageName,
362             InstantAppDigest digest, String token) {
363         final int[] shaPrefix = digest.getDigestPrefix();
364         final byte[][] digestBytes = digest.getDigestBytes();
365         boolean requiresSecondPhase = false;
366         ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
367         boolean requiresPrefixMatch = origIntent.isWebIntent() || (shaPrefix.length > 0
368                         && (origIntent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0);
369         for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
370             if (requiresPrefixMatch && instantAppResolveInfo.shouldLetInstallerDecide()) {
371                 Slog.d(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
372                         + " required; ignoring");
373                 continue;
374             }
375             byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
376             // Only include matching digests if we have a prefix and we're either dealing with a
377             // prefixed request or the resolveInfo specifies digest details.
378             if (shaPrefix.length > 0 && (requiresPrefixMatch || filterDigestBytes.length > 0)) {
379                 boolean matchFound = false;
380                 // Go in reverse order so we match the narrowest scope first.
381                 for (int i = shaPrefix.length - 1; i >= 0; --i) {
382                     if (Arrays.equals(digestBytes[i], filterDigestBytes)) {
383                         matchFound = true;
384                         break;
385                     }
386                 }
387                 if (!matchFound) {
388                     continue;
389                 }
390             }
391             // We matched a resolve info; resolve the filters to see if anything matches completely.
392             List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
393                     origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
394             if (matchFilters != null) {
395                 if (matchFilters.isEmpty()) {
396                     requiresSecondPhase = true;
397                 }
398                 if (filters == null) {
399                     filters = new ArrayList<>(matchFilters);
400                 } else {
401                     filters.addAll(matchFilters);
402                 }
403             }
404         }
405         if (filters != null && !filters.isEmpty()) {
406             return new AuxiliaryResolveInfo(token, requiresSecondPhase,
407                     createFailureIntent(origIntent, token), filters);
408         }
409         // Hash or filter mis-match; no instant apps for this domain.
410         return null;
411     }
412 
413     /**
414      * Creates a failure intent for the installer to send in the case that the instant app cannot be
415      * launched for any reason.
416      */
createFailureIntent(Intent origIntent, String token)417     private static Intent createFailureIntent(Intent origIntent, String token) {
418         final Intent failureIntent = new Intent(origIntent);
419         failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
420         failureIntent.setFlags(failureIntent.getFlags() & ~Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
421         failureIntent.setLaunchToken(token);
422         return failureIntent;
423     }
424 
425     /**
426      * Returns one of three states: <p/>
427      * <ul>
428      *     <li>{@code null} if there are no matches will not be; resolution is unnecessary.</li>
429      *     <li>An empty list signifying that a 2nd phase of resolution is required.</li>
430      *     <li>A populated list meaning that matches were found and should be sent directly to the
431      *     installer</li>
432      * </ul>
433      *
434      */
computeResolveFilters( Intent origIntent, String resolvedType, int userId, String packageName, String token, InstantAppResolveInfo instantAppInfo)435     private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
436             Intent origIntent, String resolvedType, int userId, String packageName, String token,
437             InstantAppResolveInfo instantAppInfo) {
438         if (instantAppInfo.shouldLetInstallerDecide()) {
439             return Collections.singletonList(
440                     new AuxiliaryResolveInfo.AuxiliaryFilter(
441                             instantAppInfo, null /* splitName */,
442                             instantAppInfo.getExtras()));
443         }
444         if (packageName != null
445                 && !packageName.equals(instantAppInfo.getPackageName())) {
446             return null;
447         }
448         final List<InstantAppIntentFilter> instantAppFilters =
449                 instantAppInfo.getIntentFilters();
450         if (instantAppFilters == null || instantAppFilters.isEmpty()) {
451             // No filters on web intent; no matches, 2nd phase unnecessary.
452             if (origIntent.isWebIntent()) {
453                 return null;
454             }
455             // No filters; we need to start phase two
456             if (DEBUG_INSTANT) {
457                 Log.d(TAG, "No app filters; go to phase 2");
458             }
459             return Collections.emptyList();
460         }
461         final ComponentResolver.InstantAppIntentResolver instantAppResolver =
462                 new ComponentResolver.InstantAppIntentResolver();
463         for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
464             final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
465             final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
466             if (splitFilters == null || splitFilters.isEmpty()) {
467                 continue;
468             }
469             for (int k = splitFilters.size() - 1; k >= 0; --k) {
470                 IntentFilter filter = splitFilters.get(k);
471                 Iterator<IntentFilter.AuthorityEntry> authorities =
472                         filter.authoritiesIterator();
473                 // ignore http/s-only filters.
474                 if ((authorities == null || !authorities.hasNext())
475                         && (filter.hasDataScheme("http") || filter.hasDataScheme("https"))
476                         && filter.hasAction(Intent.ACTION_VIEW)
477                         && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
478                     continue;
479                 }
480                 instantAppResolver.addFilter(
481                         new AuxiliaryResolveInfo.AuxiliaryFilter(
482                                 filter,
483                                 instantAppInfo,
484                                 instantAppFilter.getSplitName(),
485                                 instantAppInfo.getExtras()
486                         ));
487             }
488         }
489         List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
490                 instantAppResolver.queryIntent(
491                         origIntent, resolvedType, false /*defaultOnly*/, userId);
492         if (!matchedResolveInfoList.isEmpty()) {
493             if (DEBUG_INSTANT) {
494                 Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
495             }
496             return matchedResolveInfoList;
497         } else if (DEBUG_INSTANT) {
498             Log.d(TAG, "[" + token + "] No matches found"
499                     + " package: " + instantAppInfo.getPackageName()
500                     + ", versionCode: " + instantAppInfo.getVersionCode());
501         }
502         return null;
503     }
504 
logMetrics(int action, long startTime, String token, @ResolutionStatus int status)505     private static void logMetrics(int action, long startTime, String token,
506             @ResolutionStatus int status) {
507         final LogMaker logMaker = new LogMaker(action)
508                 .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
509                 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS,
510                         new Long(System.currentTimeMillis() - startTime))
511                 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token)
512                 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status));
513         getLogger().write(logMaker);
514     }
515 }
516