InstantAppResolver.java revision 709ee1551db935adb2026fae9dd799e60f784499
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 17package com.android.server.pm; 18 19import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE; 20import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO; 21import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN; 22import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_DELAY_MS; 23import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_STATUS; 24 25import android.annotation.IntDef; 26import android.annotation.NonNull; 27import android.annotation.Nullable; 28import android.app.ActivityManager; 29import android.app.PendingIntent; 30import android.content.ComponentName; 31import android.content.Context; 32import android.content.IIntentSender; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.content.IntentSender; 36import android.content.pm.ActivityInfo; 37import android.content.pm.InstantAppRequest; 38import android.content.pm.AuxiliaryResolveInfo; 39import android.content.pm.InstantAppIntentFilter; 40import android.content.pm.InstantAppResolveInfo; 41import android.content.pm.InstantAppResolveInfo.InstantAppDigest; 42import android.metrics.LogMaker; 43import android.os.Build; 44import android.os.Bundle; 45import android.os.Handler; 46import android.os.RemoteException; 47import android.util.Log; 48 49import com.android.internal.logging.MetricsLogger; 50import com.android.internal.logging.nano.MetricsProto; 51import com.android.server.pm.EphemeralResolverConnection.ConnectionException; 52import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; 53 54import java.lang.annotation.Retention; 55import java.lang.annotation.RetentionPolicy; 56import java.util.Arrays; 57import java.util.List; 58import java.util.UUID; 59 60/** @hide */ 61public abstract class InstantAppResolver { 62 private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; 63 private static final String TAG = "PackageManager"; 64 65 private static final int RESOLUTION_SUCCESS = 0; 66 private static final int RESOLUTION_FAILURE = 1; 67 /** Binding to the external service timed out */ 68 private static final int RESOLUTION_BIND_TIMEOUT = 2; 69 /** The call to retrieve an instant application response timed out */ 70 private static final int RESOLUTION_CALL_TIMEOUT = 3; 71 72 @IntDef(flag = true, prefix = { "RESOLUTION_" }, value = { 73 RESOLUTION_SUCCESS, 74 RESOLUTION_FAILURE, 75 RESOLUTION_BIND_TIMEOUT, 76 RESOLUTION_CALL_TIMEOUT, 77 }) 78 @Retention(RetentionPolicy.SOURCE) 79 public @interface ResolutionStatus {} 80 81 private static MetricsLogger sMetricsLogger; 82 private static MetricsLogger getLogger() { 83 if (sMetricsLogger == null) { 84 sMetricsLogger = new MetricsLogger(); 85 } 86 return sMetricsLogger; 87 } 88 89 public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context, 90 EphemeralResolverConnection connection, InstantAppRequest requestObj) { 91 final long startTime = System.currentTimeMillis(); 92 final String token = UUID.randomUUID().toString(); 93 if (DEBUG_EPHEMERAL) { 94 Log.d(TAG, "[" + token + "] Phase1; resolving"); 95 } 96 final Intent intent = requestObj.origIntent; 97 final InstantAppDigest digest = 98 new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/); 99 final int[] shaPrefix = digest.getDigestPrefix(); 100 AuxiliaryResolveInfo resolveInfo = null; 101 @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; 102 try { 103 final List<InstantAppResolveInfo> instantAppResolveInfoList = 104 connection.getInstantAppResolveInfoList(shaPrefix, token); 105 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { 106 resolveInfo = InstantAppResolver.filterInstantAppIntent( 107 instantAppResolveInfoList, intent, requestObj.resolvedType, 108 requestObj.userId, intent.getPackage(), digest, token); 109 } 110 } catch (ConnectionException e) { 111 if (e.failure == ConnectionException.FAILURE_BIND) { 112 resolutionStatus = RESOLUTION_BIND_TIMEOUT; 113 } else if (e.failure == ConnectionException.FAILURE_CALL) { 114 resolutionStatus = RESOLUTION_CALL_TIMEOUT; 115 } else { 116 resolutionStatus = RESOLUTION_FAILURE; 117 } 118 } 119 // Only log successful instant application resolution 120 if (requestObj.resolveForStart && resolutionStatus == RESOLUTION_SUCCESS) { 121 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, 122 resolutionStatus); 123 } 124 if (DEBUG_EPHEMERAL && resolveInfo == null) { 125 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { 126 Log.d(TAG, "[" + token + "] Phase1; bind timed out"); 127 } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { 128 Log.d(TAG, "[" + token + "] Phase1; call timed out"); 129 } else if (resolutionStatus != RESOLUTION_SUCCESS) { 130 Log.d(TAG, "[" + token + "] Phase1; service connection error"); 131 } else { 132 Log.d(TAG, "[" + token + "] Phase1; No results matched"); 133 } 134 } 135 return resolveInfo; 136 } 137 138 public static void doInstantAppResolutionPhaseTwo(Context context, 139 EphemeralResolverConnection connection, InstantAppRequest requestObj, 140 ActivityInfo instantAppInstaller, Handler callbackHandler) { 141 final long startTime = System.currentTimeMillis(); 142 final String token = requestObj.responseObj.token; 143 if (DEBUG_EPHEMERAL) { 144 Log.d(TAG, "[" + token + "] Phase2; resolving"); 145 } 146 final Intent intent = requestObj.origIntent; 147 final String hostName = intent.getData().getHost(); 148 final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/); 149 final int[] shaPrefix = digest.getDigestPrefix(); 150 151 final PhaseTwoCallback callback = new PhaseTwoCallback() { 152 @Override 153 void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList, 154 long startTime) { 155 final String packageName; 156 final String splitName; 157 final long versionCode; 158 final Intent failureIntent; 159 final Bundle extras; 160 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { 161 final AuxiliaryResolveInfo instantAppIntentInfo = 162 InstantAppResolver.filterInstantAppIntent( 163 instantAppResolveInfoList, intent, null /*resolvedType*/, 164 0 /*userId*/, intent.getPackage(), digest, token); 165 if (instantAppIntentInfo != null 166 && instantAppIntentInfo.resolveInfo != null) { 167 packageName = instantAppIntentInfo.resolveInfo.getPackageName(); 168 splitName = instantAppIntentInfo.splitName; 169 versionCode = instantAppIntentInfo.resolveInfo.getVersionCode(); 170 failureIntent = instantAppIntentInfo.failureIntent; 171 extras = instantAppIntentInfo.resolveInfo.getExtras(); 172 } else { 173 packageName = null; 174 splitName = null; 175 versionCode = -1; 176 failureIntent = null; 177 extras = null; 178 } 179 } else { 180 packageName = null; 181 splitName = null; 182 versionCode = -1; 183 failureIntent = null; 184 extras = null; 185 } 186 final Intent installerIntent = buildEphemeralInstallerIntent( 187 Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE, 188 requestObj.origIntent, 189 failureIntent, 190 requestObj.callingPackage, 191 requestObj.verificationBundle, 192 requestObj.resolvedType, 193 requestObj.userId, 194 packageName, 195 splitName, 196 requestObj.responseObj.installFailureActivity, 197 versionCode, 198 token, 199 extras, 200 false /*needsPhaseTwo*/); 201 installerIntent.setComponent(new ComponentName( 202 instantAppInstaller.packageName, instantAppInstaller.name)); 203 204 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, 205 packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); 206 207 context.startActivity(installerIntent); 208 } 209 }; 210 try { 211 connection.getInstantAppIntentFilterList( 212 shaPrefix, token, hostName, callback, callbackHandler, startTime); 213 } catch (ConnectionException e) { 214 @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; 215 if (e.failure == ConnectionException.FAILURE_BIND) { 216 resolutionStatus = RESOLUTION_BIND_TIMEOUT; 217 } 218 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, 219 resolutionStatus); 220 if (DEBUG_EPHEMERAL) { 221 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { 222 Log.d(TAG, "[" + token + "] Phase2; bind timed out"); 223 } else { 224 Log.d(TAG, "[" + token + "] Phase2; service connection error"); 225 } 226 } 227 } 228 } 229 230 /** 231 * Builds and returns an intent to launch the instant installer. 232 */ 233 public static Intent buildEphemeralInstallerIntent( 234 @NonNull String action, 235 @NonNull Intent origIntent, 236 @NonNull Intent failureIntent, 237 @NonNull String callingPackage, 238 @Nullable Bundle verificationBundle, 239 @NonNull String resolvedType, 240 int userId, 241 @NonNull String instantAppPackageName, 242 @Nullable String instantAppSplitName, 243 @Nullable ComponentName installFailureActivity, 244 long versionCode, 245 @Nullable String token, 246 @Nullable Bundle extras, 247 boolean needsPhaseTwo) { 248 // Construct the intent that launches the instant installer 249 int flags = origIntent.getFlags(); 250 final Intent intent = new Intent(action); 251 intent.setFlags(flags 252 | Intent.FLAG_ACTIVITY_NEW_TASK 253 | Intent.FLAG_ACTIVITY_CLEAR_TASK 254 | Intent.FLAG_ACTIVITY_NO_HISTORY 255 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 256 if (token != null) { 257 intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token); 258 } 259 if (origIntent.getData() != null) { 260 intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); 261 } 262 intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction()); 263 if (extras != null) { 264 intent.putExtra(Intent.EXTRA_INSTANT_APP_EXTRAS, extras); 265 } 266 267 // We have all of the data we need; just start the installer without a second phase 268 if (!needsPhaseTwo) { 269 // Intent that is launched if the package couldn't be installed for any reason. 270 if (failureIntent != null || installFailureActivity != null) { 271 try { 272 final Intent onFailureIntent; 273 if (installFailureActivity != null) { 274 onFailureIntent = new Intent(); 275 onFailureIntent.setComponent(installFailureActivity); 276 onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); 277 onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent); 278 } else { 279 onFailureIntent = failureIntent; 280 } 281 final IIntentSender failureIntentTarget = ActivityManager.getService() 282 .getIntentSender( 283 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 284 null /*token*/, null /*resultWho*/, 1 /*requestCode*/, 285 new Intent[] { onFailureIntent }, 286 new String[] { resolvedType }, 287 PendingIntent.FLAG_CANCEL_CURRENT 288 | PendingIntent.FLAG_ONE_SHOT 289 | PendingIntent.FLAG_IMMUTABLE, 290 null /*bOptions*/, userId); 291 intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, 292 new IntentSender(failureIntentTarget)); 293 } catch (RemoteException ignore) { /* ignore; same process */ } 294 } 295 296 // Intent that is launched if the package was installed successfully. 297 final Intent successIntent = new Intent(origIntent); 298 successIntent.setLaunchToken(token); 299 try { 300 final IIntentSender successIntentTarget = ActivityManager.getService() 301 .getIntentSender( 302 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 303 null /*token*/, null /*resultWho*/, 0 /*requestCode*/, 304 new Intent[] { successIntent }, 305 new String[] { resolvedType }, 306 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT 307 | PendingIntent.FLAG_IMMUTABLE, 308 null /*bOptions*/, userId); 309 intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, 310 new IntentSender(successIntentTarget)); 311 } catch (RemoteException ignore) { /* ignore; same process */ } 312 313 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); 314 intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); 315 intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) (versionCode & 0x7fffffff)); 316 intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, versionCode); 317 intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); 318 if (verificationBundle != null) { 319 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); 320 } 321 } 322 323 return intent; 324 } 325 326 private static AuxiliaryResolveInfo filterInstantAppIntent( 327 List<InstantAppResolveInfo> instantAppResolveInfoList, 328 Intent origIntent, String resolvedType, int userId, String packageName, 329 InstantAppDigest digest, String token) { 330 final int[] shaPrefix = digest.getDigestPrefix(); 331 final byte[][] digestBytes = digest.getDigestBytes(); 332 final Intent failureIntent = new Intent(origIntent); 333 failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); 334 failureIntent.setLaunchToken(token); 335 // Go in reverse order so we match the narrowest scope first. 336 for (int i = shaPrefix.length - 1; i >= 0 ; --i) { 337 for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { 338 if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { 339 continue; 340 } 341 if (packageName != null 342 && !packageName.equals(instantAppInfo.getPackageName())) { 343 continue; 344 } 345 final List<InstantAppIntentFilter> instantAppFilters = 346 instantAppInfo.getIntentFilters(); 347 // No filters; we need to start phase two 348 if (instantAppFilters == null || instantAppFilters.isEmpty()) { 349 if (DEBUG_EPHEMERAL) { 350 Log.d(TAG, "No app filters; go to phase 2"); 351 } 352 return new AuxiliaryResolveInfo(instantAppInfo, 353 new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, 354 null /*splitName*/, token, true /*needsPhase2*/, 355 null /*failureIntent*/); 356 } 357 // We have a domain match; resolve the filters to see if anything matches. 358 final PackageManagerService.EphemeralIntentResolver instantAppResolver = 359 new PackageManagerService.EphemeralIntentResolver(); 360 for (int j = instantAppFilters.size() - 1; j >= 0; --j) { 361 final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); 362 final List<IntentFilter> splitFilters = instantAppFilter.getFilters(); 363 if (splitFilters == null || splitFilters.isEmpty()) { 364 continue; 365 } 366 for (int k = splitFilters.size() - 1; k >= 0; --k) { 367 final AuxiliaryResolveInfo intentInfo = 368 new AuxiliaryResolveInfo(instantAppInfo, 369 splitFilters.get(k), instantAppFilter.getSplitName(), 370 token, false /*needsPhase2*/, failureIntent); 371 instantAppResolver.addFilter(intentInfo); 372 } 373 } 374 List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent( 375 origIntent, resolvedType, false /*defaultOnly*/, userId); 376 if (!matchedResolveInfoList.isEmpty()) { 377 if (DEBUG_EPHEMERAL) { 378 final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); 379 Log.d(TAG, "[" + token + "] Found match;" 380 + " package: " + info.packageName 381 + ", split: " + info.splitName 382 + ", versionCode: " + info.versionCode); 383 } 384 return matchedResolveInfoList.get(0); 385 } else if (DEBUG_EPHEMERAL) { 386 Log.d(TAG, "[" + token + "] No matches found" 387 + " package: " + instantAppInfo.getPackageName() 388 + ", versionCode: " + instantAppInfo.getVersionCode()); 389 } 390 } 391 } 392 // Hash or filter mis-match; no instant apps for this domain. 393 return null; 394 } 395 396 private static void logMetrics(int action, long startTime, String token, 397 @ResolutionStatus int status) { 398 final LogMaker logMaker = new LogMaker(action) 399 .setType(MetricsProto.MetricsEvent.TYPE_ACTION) 400 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS, 401 new Long(System.currentTimeMillis() - startTime)) 402 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token) 403 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status)); 404 getLogger().write(logMaker); 405 } 406} 407