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