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