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