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 // Only log successful instant application resolution 125 if (requestObj.resolveForStart && resolutionStatus == RESOLUTION_SUCCESS) { 126 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, 127 resolutionStatus); 128 } 129 if (DEBUG_EPHEMERAL && resolveInfo == null) { 130 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { 131 Log.d(TAG, "[" + token + "] Phase1; bind timed out"); 132 } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { 133 Log.d(TAG, "[" + token + "] Phase1; call timed out"); 134 } else if (resolutionStatus != RESOLUTION_SUCCESS) { 135 Log.d(TAG, "[" + token + "] Phase1; service connection error"); 136 } else { 137 Log.d(TAG, "[" + token + "] Phase1; No results matched"); 138 } 139 } 140 return resolveInfo; 141 } 142 143 public static void doInstantAppResolutionPhaseTwo(Context context, 144 EphemeralResolverConnection connection, InstantAppRequest requestObj, 145 ActivityInfo instantAppInstaller, Handler callbackHandler) { 146 final long startTime = System.currentTimeMillis(); 147 final String token = requestObj.responseObj.token; 148 if (DEBUG_EPHEMERAL) { 149 Log.d(TAG, "[" + token + "] Phase2; resolving"); 150 } 151 final Intent intent = requestObj.origIntent; 152 final String hostName = intent.getData().getHost(); 153 final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/); 154 final int[] shaPrefix = digest.getDigestPrefix(); 155 156 final PhaseTwoCallback callback = new PhaseTwoCallback() { 157 @Override 158 void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList, 159 long startTime) { 160 final String packageName; 161 final String splitName; 162 final int versionCode; 163 final Intent failureIntent; 164 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { 165 final AuxiliaryResolveInfo instantAppIntentInfo = 166 InstantAppResolver.filterInstantAppIntent( 167 instantAppResolveInfoList, intent, null /*resolvedType*/, 168 0 /*userId*/, intent.getPackage(), digest, token); 169 if (instantAppIntentInfo != null 170 && instantAppIntentInfo.resolveInfo != null) { 171 packageName = instantAppIntentInfo.resolveInfo.getPackageName(); 172 splitName = instantAppIntentInfo.splitName; 173 versionCode = instantAppIntentInfo.resolveInfo.getVersionCode(); 174 failureIntent = instantAppIntentInfo.failureIntent; 175 } else { 176 packageName = null; 177 splitName = null; 178 versionCode = -1; 179 failureIntent = null; 180 } 181 } else { 182 packageName = null; 183 splitName = null; 184 versionCode = -1; 185 failureIntent = null; 186 } 187 final Intent installerIntent = buildEphemeralInstallerIntent( 188 Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE, 189 requestObj.origIntent, 190 failureIntent, 191 requestObj.callingPackage, 192 requestObj.verificationBundle, 193 requestObj.resolvedType, 194 requestObj.userId, 195 packageName, 196 splitName, 197 requestObj.responseObj.installFailureActivity, 198 versionCode, 199 token, 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 int versionCode, 245 @Nullable String token, 246 boolean needsPhaseTwo) { 247 // Construct the intent that launches the instant installer 248 int flags = origIntent.getFlags(); 249 final Intent intent = new Intent(action); 250 intent.setFlags(flags 251 | Intent.FLAG_ACTIVITY_NEW_TASK 252 | Intent.FLAG_ACTIVITY_CLEAR_TASK 253 | Intent.FLAG_ACTIVITY_NO_HISTORY 254 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 255 if (token != null) { 256 intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token); 257 } 258 if (origIntent.getData() != null) { 259 intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); 260 } 261 262 // We have all of the data we need; just start the installer without a second phase 263 if (!needsPhaseTwo) { 264 // Intent that is launched if the package couldn't be installed for any reason. 265 if (failureIntent != null || installFailureActivity != null) { 266 try { 267 final Intent onFailureIntent; 268 if (installFailureActivity != null) { 269 onFailureIntent = new Intent(); 270 onFailureIntent.setComponent(installFailureActivity); 271 onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); 272 onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent); 273 } else { 274 onFailureIntent = failureIntent; 275 } 276 final IIntentSender failureIntentTarget = ActivityManager.getService() 277 .getIntentSender( 278 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 279 null /*token*/, null /*resultWho*/, 1 /*requestCode*/, 280 new Intent[] { onFailureIntent }, 281 new String[] { resolvedType }, 282 PendingIntent.FLAG_CANCEL_CURRENT 283 | PendingIntent.FLAG_ONE_SHOT 284 | PendingIntent.FLAG_IMMUTABLE, 285 null /*bOptions*/, userId); 286 intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, 287 new IntentSender(failureIntentTarget)); 288 } catch (RemoteException ignore) { /* ignore; same process */ } 289 } 290 291 // Intent that is launched if the package was installed successfully. 292 final Intent successIntent = new Intent(origIntent); 293 successIntent.setLaunchToken(token); 294 try { 295 final IIntentSender successIntentTarget = ActivityManager.getService() 296 .getIntentSender( 297 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 298 null /*token*/, null /*resultWho*/, 0 /*requestCode*/, 299 new Intent[] { successIntent }, 300 new String[] { resolvedType }, 301 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT 302 | PendingIntent.FLAG_IMMUTABLE, 303 null /*bOptions*/, userId); 304 intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, 305 new IntentSender(successIntentTarget)); 306 } catch (RemoteException ignore) { /* ignore; same process */ } 307 308 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); 309 intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); 310 intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode); 311 intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); 312 if (verificationBundle != null) { 313 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); 314 } 315 } 316 317 return intent; 318 } 319 320 private static AuxiliaryResolveInfo filterInstantAppIntent( 321 List<InstantAppResolveInfo> instantAppResolveInfoList, 322 Intent origIntent, String resolvedType, int userId, String packageName, 323 InstantAppDigest digest, String token) { 324 final int[] shaPrefix = digest.getDigestPrefix(); 325 final byte[][] digestBytes = digest.getDigestBytes(); 326 final Intent failureIntent = new Intent(origIntent); 327 failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); 328 failureIntent.setLaunchToken(token); 329 // Go in reverse order so we match the narrowest scope first. 330 for (int i = shaPrefix.length - 1; i >= 0 ; --i) { 331 for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { 332 if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { 333 continue; 334 } 335 if (packageName != null 336 && !packageName.equals(instantAppInfo.getPackageName())) { 337 continue; 338 } 339 final List<InstantAppIntentFilter> instantAppFilters = 340 instantAppInfo.getIntentFilters(); 341 // No filters; we need to start phase two 342 if (instantAppFilters == null || instantAppFilters.isEmpty()) { 343 if (DEBUG_EPHEMERAL) { 344 Log.d(TAG, "No app filters; go to phase 2"); 345 } 346 return new AuxiliaryResolveInfo(instantAppInfo, 347 new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, 348 null /*splitName*/, token, true /*needsPhase2*/, 349 null /*failureIntent*/); 350 } 351 // We have a domain match; resolve the filters to see if anything matches. 352 final PackageManagerService.EphemeralIntentResolver instantAppResolver = 353 new PackageManagerService.EphemeralIntentResolver(); 354 for (int j = instantAppFilters.size() - 1; j >= 0; --j) { 355 final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); 356 final List<IntentFilter> splitFilters = instantAppFilter.getFilters(); 357 if (splitFilters == null || splitFilters.isEmpty()) { 358 continue; 359 } 360 for (int k = splitFilters.size() - 1; k >= 0; --k) { 361 final AuxiliaryResolveInfo intentInfo = 362 new AuxiliaryResolveInfo(instantAppInfo, 363 splitFilters.get(k), instantAppFilter.getSplitName(), 364 token, false /*needsPhase2*/, failureIntent); 365 instantAppResolver.addFilter(intentInfo); 366 } 367 } 368 List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent( 369 origIntent, resolvedType, false /*defaultOnly*/, userId); 370 if (!matchedResolveInfoList.isEmpty()) { 371 if (DEBUG_EPHEMERAL) { 372 final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); 373 Log.d(TAG, "[" + token + "] Found match;" 374 + " package: " + info.packageName 375 + ", split: " + info.splitName 376 + ", versionCode: " + info.versionCode); 377 } 378 return matchedResolveInfoList.get(0); 379 } else if (DEBUG_EPHEMERAL) { 380 Log.d(TAG, "[" + token + "] No matches found" 381 + " package: " + instantAppInfo.getPackageName() 382 + ", versionCode: " + instantAppInfo.getVersionCode()); 383 } 384 } 385 } 386 // Hash or filter mis-match; no instant apps for this domain. 387 return null; 388 } 389 390 private static void logMetrics(int action, long startTime, String token, 391 @ResolutionStatus int status) { 392 final LogMaker logMaker = new LogMaker(action) 393 .setType(MetricsProto.MetricsEvent.TYPE_ACTION) 394 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS, 395 new Long(System.currentTimeMillis() - startTime)) 396 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token) 397 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status)); 398 getLogger().write(logMaker); 399 } 400} 401