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 com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE; 20 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO; 21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN; 22 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_DELAY_MS; 23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_STATUS; 24 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityManager; 29 import android.app.PendingIntent; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.IIntentSender; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.content.IntentSender; 36 import android.content.pm.ActivityInfo; 37 import android.content.pm.InstantAppRequest; 38 import android.content.pm.AuxiliaryResolveInfo; 39 import android.content.pm.InstantAppIntentFilter; 40 import android.content.pm.InstantAppResolveInfo; 41 import android.content.pm.InstantAppResolveInfo.InstantAppDigest; 42 import android.metrics.LogMaker; 43 import android.os.Binder; 44 import android.os.Build; 45 import android.os.Bundle; 46 import android.os.Handler; 47 import android.os.RemoteException; 48 import android.util.Log; 49 import android.util.Pair; 50 import android.util.Slog; 51 52 import com.android.internal.logging.MetricsLogger; 53 import com.android.internal.logging.nano.MetricsProto; 54 import com.android.server.pm.EphemeralResolverConnection.ConnectionException; 55 import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; 56 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.List; 62 import java.util.UUID; 63 import java.util.concurrent.TimeoutException; 64 65 /** @hide */ 66 public 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; getLogger()87 private static MetricsLogger getLogger() { 88 if (sMetricsLogger == null) { 89 sMetricsLogger = new MetricsLogger(); 90 } 91 return sMetricsLogger; 92 } 93 doInstantAppResolutionPhaseOne(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj)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 (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 doInstantAppResolutionPhaseTwo(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler)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 versionCode, 198 token, 199 false /*needsPhaseTwo*/); 200 installerIntent.setComponent(new ComponentName( 201 instantAppInstaller.packageName, instantAppInstaller.name)); 202 203 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, 204 packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); 205 206 context.startActivity(installerIntent); 207 } 208 }; 209 try { 210 connection.getInstantAppIntentFilterList( 211 shaPrefix, token, hostName, callback, callbackHandler, startTime); 212 } catch (ConnectionException e) { 213 @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; 214 if (e.failure == ConnectionException.FAILURE_BIND) { 215 resolutionStatus = RESOLUTION_BIND_TIMEOUT; 216 } 217 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, 218 resolutionStatus); 219 if (DEBUG_EPHEMERAL) { 220 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { 221 Log.d(TAG, "[" + token + "] Phase2; bind timed out"); 222 } else { 223 Log.d(TAG, "[" + token + "] Phase2; service connection error"); 224 } 225 } 226 } 227 } 228 229 /** 230 * Builds and returns an intent to launch the instant installer. 231 */ buildEphemeralInstallerIntent( @onNull String action, @NonNull Intent origIntent, @NonNull Intent failureIntent, @NonNull String callingPackage, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, @NonNull String instantAppPackageName, @Nullable String instantAppSplitName, int versionCode, @Nullable String token, boolean needsPhaseTwo)232 public static Intent buildEphemeralInstallerIntent( 233 @NonNull String action, 234 @NonNull Intent origIntent, 235 @NonNull Intent failureIntent, 236 @NonNull String callingPackage, 237 @Nullable Bundle verificationBundle, 238 @NonNull String resolvedType, 239 int userId, 240 @NonNull String instantAppPackageName, 241 @Nullable String instantAppSplitName, 242 int versionCode, 243 @Nullable String token, 244 boolean needsPhaseTwo) { 245 // Construct the intent that launches the instant installer 246 int flags = origIntent.getFlags(); 247 final Intent intent = new Intent(action); 248 intent.setFlags(flags 249 | Intent.FLAG_ACTIVITY_NEW_TASK 250 | Intent.FLAG_ACTIVITY_CLEAR_TASK 251 | Intent.FLAG_ACTIVITY_NO_HISTORY 252 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 253 if (token != null) { 254 intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token); 255 } 256 if (origIntent.getData() != null) { 257 intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); 258 } 259 260 // We have all of the data we need; just start the installer without a second phase 261 if (!needsPhaseTwo) { 262 // Intent that is launched if the package couldn't be installed for any reason. 263 if (failureIntent != null) { 264 try { 265 final IIntentSender failureIntentTarget = ActivityManager.getService() 266 .getIntentSender( 267 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 268 null /*token*/, null /*resultWho*/, 1 /*requestCode*/, 269 new Intent[] { failureIntent }, 270 new String[] { resolvedType }, 271 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT 272 | PendingIntent.FLAG_IMMUTABLE, 273 null /*bOptions*/, userId); 274 intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, 275 new IntentSender(failureIntentTarget)); 276 } catch (RemoteException ignore) { /* ignore; same process */ } 277 } 278 279 // Intent that is launched if the package was installed successfully. 280 final Intent successIntent = new Intent(origIntent); 281 successIntent.setLaunchToken(token); 282 try { 283 final IIntentSender successIntentTarget = ActivityManager.getService() 284 .getIntentSender( 285 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, 286 null /*token*/, null /*resultWho*/, 0 /*requestCode*/, 287 new Intent[] { successIntent }, 288 new String[] { resolvedType }, 289 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT 290 | PendingIntent.FLAG_IMMUTABLE, 291 null /*bOptions*/, userId); 292 intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, 293 new IntentSender(successIntentTarget)); 294 } catch (RemoteException ignore) { /* ignore; same process */ } 295 296 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); 297 intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); 298 intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode); 299 intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); 300 if (verificationBundle != null) { 301 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); 302 } 303 } 304 305 return intent; 306 } 307 filterInstantAppIntent( List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent, String resolvedType, int userId, String packageName, InstantAppDigest digest, String token)308 private static AuxiliaryResolveInfo filterInstantAppIntent( 309 List<InstantAppResolveInfo> instantAppResolveInfoList, 310 Intent origIntent, String resolvedType, int userId, String packageName, 311 InstantAppDigest digest, String token) { 312 final int[] shaPrefix = digest.getDigestPrefix(); 313 final byte[][] digestBytes = digest.getDigestBytes(); 314 final Intent failureIntent = new Intent(origIntent); 315 failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); 316 failureIntent.setLaunchToken(token); 317 // Go in reverse order so we match the narrowest scope first. 318 for (int i = shaPrefix.length - 1; i >= 0 ; --i) { 319 for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { 320 if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { 321 continue; 322 } 323 if (packageName != null 324 && !packageName.equals(instantAppInfo.getPackageName())) { 325 continue; 326 } 327 final List<InstantAppIntentFilter> instantAppFilters = 328 instantAppInfo.getIntentFilters(); 329 // No filters; we need to start phase two 330 if (instantAppFilters == null || instantAppFilters.isEmpty()) { 331 if (DEBUG_EPHEMERAL) { 332 Log.d(TAG, "No app filters; go to phase 2"); 333 } 334 return new AuxiliaryResolveInfo(instantAppInfo, 335 new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, 336 null /*splitName*/, token, true /*needsPhase2*/, 337 null /*failureIntent*/); 338 } 339 // We have a domain match; resolve the filters to see if anything matches. 340 final PackageManagerService.EphemeralIntentResolver instantAppResolver = 341 new PackageManagerService.EphemeralIntentResolver(); 342 for (int j = instantAppFilters.size() - 1; j >= 0; --j) { 343 final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); 344 final List<IntentFilter> splitFilters = instantAppFilter.getFilters(); 345 if (splitFilters == null || splitFilters.isEmpty()) { 346 continue; 347 } 348 for (int k = splitFilters.size() - 1; k >= 0; --k) { 349 final AuxiliaryResolveInfo intentInfo = 350 new AuxiliaryResolveInfo(instantAppInfo, 351 splitFilters.get(k), instantAppFilter.getSplitName(), 352 token, false /*needsPhase2*/, failureIntent); 353 instantAppResolver.addFilter(intentInfo); 354 } 355 } 356 List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent( 357 origIntent, resolvedType, false /*defaultOnly*/, userId); 358 if (!matchedResolveInfoList.isEmpty()) { 359 if (DEBUG_EPHEMERAL) { 360 final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); 361 Log.d(TAG, "[" + token + "] Found match;" 362 + " package: " + info.packageName 363 + ", split: " + info.splitName 364 + ", versionCode: " + info.versionCode); 365 } 366 return matchedResolveInfoList.get(0); 367 } else if (DEBUG_EPHEMERAL) { 368 Log.d(TAG, "[" + token + "] No matches found" 369 + " package: " + instantAppInfo.getPackageName() 370 + ", versionCode: " + instantAppInfo.getVersionCode()); 371 } 372 } 373 } 374 // Hash or filter mis-match; no instant apps for this domain. 375 return null; 376 } 377 logMetrics(int action, long startTime, String token, @ResolutionStatus int status)378 private static void logMetrics(int action, long startTime, String token, 379 @ResolutionStatus int status) { 380 final LogMaker logMaker = new LogMaker(action) 381 .setType(MetricsProto.MetricsEvent.TYPE_ACTION) 382 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS, 383 new Long(System.currentTimeMillis() - startTime)) 384 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token) 385 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status)); 386 getLogger().write(logMaker); 387 } 388 } 389