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