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