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