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