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