blob: 9a5dd5e04b0720e39c9211ecfbc0485dcc51655c [file] [log] [blame]
Todd Kennedy01ad0c72016-11-11 15:33:12 -08001/*
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
Patrick Baumann3e0b8ee2018-04-26 12:03:15 -070019import static android.content.Intent.FLAG_ACTIVITY_MATCH_EXTERNAL;
20
Todd Kennedy50d946c12017-03-17 13:55:38 -070021import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE;
22import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO;
23import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN;
24import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_DELAY_MS;
25import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_STATUS;
26
Todd Kennedybdf2a802017-05-08 16:09:42 -070027import android.annotation.IntDef;
Todd Kennedye9910222017-02-21 16:00:11 -080028import android.annotation.NonNull;
29import android.annotation.Nullable;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080030import android.app.ActivityManager;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080031import android.app.PendingIntent;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.IIntentSender;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.content.IntentSender;
38import android.content.pm.ActivityInfo;
Todd Kennedy1fb34042017-03-01 13:56:58 -080039import android.content.pm.InstantAppRequest;
Todd Kennedye9910222017-02-21 16:00:11 -080040import android.content.pm.AuxiliaryResolveInfo;
Todd Kennedy1fb34042017-03-01 13:56:58 -080041import android.content.pm.InstantAppIntentFilter;
42import android.content.pm.InstantAppResolveInfo;
43import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
Todd Kennedy50d946c12017-03-17 13:55:38 -070044import android.metrics.LogMaker;
Patrick Baumann577d4022018-01-31 16:55:10 +000045import android.net.Uri;
Todd Kennedyd3c51062017-03-17 16:03:29 -070046import android.os.Build;
Chad Brubaker06068612017-04-06 09:43:47 -070047import android.os.Bundle;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080048import android.os.Handler;
49import android.os.RemoteException;
50import android.util.Log;
Patrick Baumann577d4022018-01-31 16:55:10 +000051import android.util.Slog;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080052
Todd Kennedy50d946c12017-03-17 13:55:38 -070053import com.android.internal.logging.MetricsLogger;
54import com.android.internal.logging.nano.MetricsProto;
Patrick Baumann43c97a02018-01-31 20:09:03 +000055import com.android.server.pm.InstantAppResolverConnection.ConnectionException;
56import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080057
Todd Kennedybdf2a802017-05-08 16:09:42 -070058import java.lang.annotation.Retention;
59import java.lang.annotation.RetentionPolicy;
Patrick Baumann577d4022018-01-31 16:55:10 +000060import java.util.ArrayList;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080061import java.util.Arrays;
Patrick Baumann577d4022018-01-31 16:55:10 +000062import java.util.Collections;
63import java.util.Iterator;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080064import java.util.List;
Patrick Baumann577d4022018-01-31 16:55:10 +000065import java.util.Set;
Todd Kennedy01ad0c72016-11-11 15:33:12 -080066import java.util.UUID;
67
68/** @hide */
Todd Kennedy1fb34042017-03-01 13:56:58 -080069public abstract class InstantAppResolver {
Patrick Baumann43c97a02018-01-31 20:09:03 +000070 private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
Todd Kennedyd3c51062017-03-17 16:03:29 -070071 private static final String TAG = "PackageManager";
72
Todd Kennedybdf2a802017-05-08 16:09:42 -070073 private static final int RESOLUTION_SUCCESS = 0;
74 private static final int RESOLUTION_FAILURE = 1;
75 /** Binding to the external service timed out */
76 private static final int RESOLUTION_BIND_TIMEOUT = 2;
77 /** The call to retrieve an instant application response timed out */
78 private static final int RESOLUTION_CALL_TIMEOUT = 3;
79
80 @IntDef(flag = true, prefix = { "RESOLUTION_" }, value = {
81 RESOLUTION_SUCCESS,
82 RESOLUTION_FAILURE,
83 RESOLUTION_BIND_TIMEOUT,
84 RESOLUTION_CALL_TIMEOUT,
85 })
86 @Retention(RetentionPolicy.SOURCE)
87 public @interface ResolutionStatus {}
Todd Kennedy50d946c12017-03-17 13:55:38 -070088
89 private static MetricsLogger sMetricsLogger;
Patrick Baumann577d4022018-01-31 16:55:10 +000090
Todd Kennedy50d946c12017-03-17 13:55:38 -070091 private static MetricsLogger getLogger() {
92 if (sMetricsLogger == null) {
93 sMetricsLogger = new MetricsLogger();
94 }
95 return sMetricsLogger;
96 }
97
Patrick Baumann577d4022018-01-31 16:55:10 +000098 /**
99 * Returns an intent with potential PII removed from the original intent. Fields removed
100 * include extras and the host + path of the data, if defined.
101 */
102 public static Intent sanitizeIntent(Intent origIntent) {
103 final Intent sanitizedIntent;
104 sanitizedIntent = new Intent(origIntent.getAction());
105 Set<String> categories = origIntent.getCategories();
106 if (categories != null) {
107 for (String category : categories) {
108 sanitizedIntent.addCategory(category);
109 }
110 }
111 Uri sanitizedUri = origIntent.getData() == null
112 ? null
113 : Uri.fromParts(origIntent.getScheme(), "", "");
114 sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType());
115 sanitizedIntent.addFlags(origIntent.getFlags());
116 sanitizedIntent.setPackage(origIntent.getPackage());
117 return sanitizedIntent;
118 }
119
120 public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
Patrick Baumann43c97a02018-01-31 20:09:03 +0000121 InstantAppResolverConnection connection, InstantAppRequest requestObj) {
Todd Kennedy50d946c12017-03-17 13:55:38 -0700122 final long startTime = System.currentTimeMillis();
123 final String token = UUID.randomUUID().toString();
Patrick Baumann43c97a02018-01-31 20:09:03 +0000124 if (DEBUG_INSTANT) {
Todd Kennedybdf2a802017-05-08 16:09:42 -0700125 Log.d(TAG, "[" + token + "] Phase1; resolving");
Todd Kennedy46b4f2b2017-04-21 12:20:03 -0700126 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000127 final Intent origIntent = requestObj.origIntent;
128 final Intent sanitizedIntent = sanitizeIntent(origIntent);
129
Todd Kennedybdf2a802017-05-08 16:09:42 -0700130 AuxiliaryResolveInfo resolveInfo = null;
131 @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
132 try {
133 final List<InstantAppResolveInfo> instantAppResolveInfoList =
Patrick Baumann4db6bc12018-02-06 09:55:36 -0800134 connection.getInstantAppResolveInfoList(sanitizedIntent,
135 requestObj.digest.getDigestPrefixSecure(), token);
Todd Kennedybdf2a802017-05-08 16:09:42 -0700136 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
137 resolveInfo = InstantAppResolver.filterInstantAppIntent(
Patrick Baumann577d4022018-01-31 16:55:10 +0000138 instantAppResolveInfoList, origIntent, requestObj.resolvedType,
Patrick Baumann4db6bc12018-02-06 09:55:36 -0800139 requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
Todd Kennedyd3c51062017-03-17 16:03:29 -0700140 }
Todd Kennedybdf2a802017-05-08 16:09:42 -0700141 } catch (ConnectionException e) {
142 if (e.failure == ConnectionException.FAILURE_BIND) {
143 resolutionStatus = RESOLUTION_BIND_TIMEOUT;
144 } else if (e.failure == ConnectionException.FAILURE_CALL) {
145 resolutionStatus = RESOLUTION_CALL_TIMEOUT;
146 } else {
147 resolutionStatus = RESOLUTION_FAILURE;
148 }
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800149 }
Todd Kennedyf4c38552017-06-12 10:33:32 -0700150 // Only log successful instant application resolution
Todd Kennedy6ebabca2017-08-22 10:02:12 -0700151 if (requestObj.resolveForStart && resolutionStatus == RESOLUTION_SUCCESS) {
Todd Kennedyf4c38552017-06-12 10:33:32 -0700152 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
153 resolutionStatus);
154 }
Patrick Baumann43c97a02018-01-31 20:09:03 +0000155 if (DEBUG_INSTANT && resolveInfo == null) {
Todd Kennedybdf2a802017-05-08 16:09:42 -0700156 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
157 Log.d(TAG, "[" + token + "] Phase1; bind timed out");
158 } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) {
159 Log.d(TAG, "[" + token + "] Phase1; call timed out");
160 } else if (resolutionStatus != RESOLUTION_SUCCESS) {
161 Log.d(TAG, "[" + token + "] Phase1; service connection error");
162 } else {
163 Log.d(TAG, "[" + token + "] Phase1; No results matched");
164 }
Todd Kennedy46b4f2b2017-04-21 12:20:03 -0700165 }
Patrick Baumanna6862c22018-05-03 13:16:01 -0700166 // if the match external flag is set, return an empty resolve info instead of a null result.
167 if (resolveInfo == null && (origIntent.getFlags() & FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
168 return new AuxiliaryResolveInfo(token, false, createFailureIntent(origIntent, token),
169 null /* filters */);
170 }
Todd Kennedy50d946c12017-03-17 13:55:38 -0700171 return resolveInfo;
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800172 }
173
Todd Kennedy1fb34042017-03-01 13:56:58 -0800174 public static void doInstantAppResolutionPhaseTwo(Context context,
Patrick Baumann43c97a02018-01-31 20:09:03 +0000175 InstantAppResolverConnection connection, InstantAppRequest requestObj,
Todd Kennedy1fb34042017-03-01 13:56:58 -0800176 ActivityInfo instantAppInstaller, Handler callbackHandler) {
Todd Kennedy50d946c12017-03-17 13:55:38 -0700177 final long startTime = System.currentTimeMillis();
Todd Kennedy46b4f2b2017-04-21 12:20:03 -0700178 final String token = requestObj.responseObj.token;
Patrick Baumann43c97a02018-01-31 20:09:03 +0000179 if (DEBUG_INSTANT) {
Todd Kennedybdf2a802017-05-08 16:09:42 -0700180 Log.d(TAG, "[" + token + "] Phase2; resolving");
Todd Kennedy46b4f2b2017-04-21 12:20:03 -0700181 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000182 final Intent origIntent = requestObj.origIntent;
183 final Intent sanitizedIntent = sanitizeIntent(origIntent);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800184
185 final PhaseTwoCallback callback = new PhaseTwoCallback() {
186 @Override
Todd Kennedy1fb34042017-03-01 13:56:58 -0800187 void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList,
Todd Kennedy50d946c12017-03-17 13:55:38 -0700188 long startTime) {
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700189 final Intent failureIntent;
Todd Kennedy1fb34042017-03-01 13:56:58 -0800190 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
191 final AuxiliaryResolveInfo instantAppIntentInfo =
192 InstantAppResolver.filterInstantAppIntent(
Patrick Baumann577d4022018-01-31 16:55:10 +0000193 instantAppResolveInfoList, origIntent, null /*resolvedType*/,
Patrick Baumann4db6bc12018-02-06 09:55:36 -0800194 0 /*userId*/, origIntent.getPackage(), requestObj.digest,
195 token);
Patrick Baumann577d4022018-01-31 16:55:10 +0000196 if (instantAppIntentInfo != null) {
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700197 failureIntent = instantAppIntentInfo.failureIntent;
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800198 } else {
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700199 failureIntent = null;
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800200 }
201 } else {
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700202 failureIntent = null;
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800203 }
204 final Intent installerIntent = buildEphemeralInstallerIntent(
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800205 requestObj.origIntent,
Patrick Baumann577d4022018-01-31 16:55:10 +0000206 sanitizedIntent,
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700207 failureIntent,
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800208 requestObj.callingPackage,
Chad Brubaker06068612017-04-06 09:43:47 -0700209 requestObj.verificationBundle,
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800210 requestObj.resolvedType,
211 requestObj.userId,
Todd Kennedyd0084f72017-07-28 13:56:14 -0700212 requestObj.responseObj.installFailureActivity,
Todd Kennedy50d946c12017-03-17 13:55:38 -0700213 token,
Patrick Baumann577d4022018-01-31 16:55:10 +0000214 false /*needsPhaseTwo*/,
215 requestObj.responseObj.filters);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800216 installerIntent.setComponent(new ComponentName(
Todd Kennedy1fb34042017-03-01 13:56:58 -0800217 instantAppInstaller.packageName, instantAppInstaller.name));
Todd Kennedy50d946c12017-03-17 13:55:38 -0700218
219 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
Patrick Baumann577d4022018-01-31 16:55:10 +0000220 requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
Todd Kennedy50d946c12017-03-17 13:55:38 -0700221
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800222 context.startActivity(installerIntent);
223 }
224 };
Todd Kennedybdf2a802017-05-08 16:09:42 -0700225 try {
Patrick Baumann4db6bc12018-02-06 09:55:36 -0800226 connection.getInstantAppIntentFilterList(sanitizedIntent,
227 requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler,
228 startTime);
Todd Kennedybdf2a802017-05-08 16:09:42 -0700229 } catch (ConnectionException e) {
230 @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
231 if (e.failure == ConnectionException.FAILURE_BIND) {
232 resolutionStatus = RESOLUTION_BIND_TIMEOUT;
233 }
234 logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
235 resolutionStatus);
Patrick Baumann43c97a02018-01-31 20:09:03 +0000236 if (DEBUG_INSTANT) {
Todd Kennedybdf2a802017-05-08 16:09:42 -0700237 if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
238 Log.d(TAG, "[" + token + "] Phase2; bind timed out");
239 } else {
240 Log.d(TAG, "[" + token + "] Phase2; service connection error");
241 }
242 }
243 }
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800244 }
245
246 /**
Todd Kennedy1fb34042017-03-01 13:56:58 -0800247 * Builds and returns an intent to launch the instant installer.
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800248 */
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700249 public static Intent buildEphemeralInstallerIntent(
250 @NonNull Intent origIntent,
Patrick Baumann577d4022018-01-31 16:55:10 +0000251 @NonNull Intent sanitizedIntent,
252 @Nullable Intent failureIntent,
Todd Kennedye9910222017-02-21 16:00:11 -0800253 @NonNull String callingPackage,
Chad Brubaker06068612017-04-06 09:43:47 -0700254 @Nullable Bundle verificationBundle,
Todd Kennedye9910222017-02-21 16:00:11 -0800255 @NonNull String resolvedType,
256 int userId,
Todd Kennedyd0084f72017-07-28 13:56:14 -0700257 @Nullable ComponentName installFailureActivity,
Todd Kennedye9910222017-02-21 16:00:11 -0800258 @Nullable String token,
Patrick Baumann577d4022018-01-31 16:55:10 +0000259 boolean needsPhaseTwo,
260 List<AuxiliaryResolveInfo.AuxiliaryFilter> filters) {
Todd Kennedy1fb34042017-03-01 13:56:58 -0800261 // Construct the intent that launches the instant installer
Todd Kennedye9910222017-02-21 16:00:11 -0800262 int flags = origIntent.getFlags();
Patrick Baumann577d4022018-01-31 16:55:10 +0000263 final Intent intent = new Intent();
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800264 intent.setFlags(flags
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800265 | Intent.FLAG_ACTIVITY_NO_HISTORY
266 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Todd Kennedye9910222017-02-21 16:00:11 -0800267 if (token != null) {
Patrick Baumann159cd022018-01-11 13:25:05 -0800268 // TODO(b/72700831): remove populating old extra
Todd Kennedye9910222017-02-21 16:00:11 -0800269 intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token);
Patrick Baumann159cd022018-01-11 13:25:05 -0800270 intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token);
Todd Kennedye9910222017-02-21 16:00:11 -0800271 }
272 if (origIntent.getData() != null) {
Patrick Baumann159cd022018-01-11 13:25:05 -0800273 // TODO(b/72700831): remove populating old extra
Todd Kennedye9910222017-02-21 16:00:11 -0800274 intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
Patrick Baumann159cd022018-01-11 13:25:05 -0800275 intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost());
Todd Kennedye9910222017-02-21 16:00:11 -0800276 }
Patrick Baumannb4165d72017-12-04 11:29:24 -0800277 intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
Patrick Baumann577d4022018-01-31 16:55:10 +0000278 intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800279
Patrick Baumann577d4022018-01-31 16:55:10 +0000280 if (needsPhaseTwo) {
281 intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE);
282 } else {
283 // We have all of the data we need; just start the installer without a second phase
Todd Kennedyd0084f72017-07-28 13:56:14 -0700284 if (failureIntent != null || installFailureActivity != null) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000285 // Intent that is launched if the package couldn't be installed for any reason.
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700286 try {
Todd Kennedyd0084f72017-07-28 13:56:14 -0700287 final Intent onFailureIntent;
288 if (installFailureActivity != null) {
289 onFailureIntent = new Intent();
290 onFailureIntent.setComponent(installFailureActivity);
Patrick Baumann577d4022018-01-31 16:55:10 +0000291 if (filters != null && filters.size() == 1) {
292 onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
293 filters.get(0).splitName);
294 }
Todd Kennedyd0084f72017-07-28 13:56:14 -0700295 onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
296 } else {
297 onFailureIntent = failureIntent;
298 }
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700299 final IIntentSender failureIntentTarget = ActivityManager.getService()
300 .getIntentSender(
301 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
302 null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
Todd Kennedyd0084f72017-07-28 13:56:14 -0700303 new Intent[] { onFailureIntent },
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700304 new String[] { resolvedType },
Todd Kennedyd0084f72017-07-28 13:56:14 -0700305 PendingIntent.FLAG_CANCEL_CURRENT
306 | PendingIntent.FLAG_ONE_SHOT
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700307 | PendingIntent.FLAG_IMMUTABLE,
308 null /*bOptions*/, userId);
Patrick Baumann159cd022018-01-11 13:25:05 -0800309 IntentSender failureSender = new IntentSender(failureIntentTarget);
310 // TODO(b/72700831): remove populating old extra
311 intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, failureSender);
312 intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender);
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700313 } catch (RemoteException ignore) { /* ignore; same process */ }
314 }
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800315
Todd Kennedye9910222017-02-21 16:00:11 -0800316 // Intent that is launched if the package was installed successfully.
317 final Intent successIntent = new Intent(origIntent);
Todd Kennedyb3b431302017-03-20 16:05:48 -0700318 successIntent.setLaunchToken(token);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800319 try {
Todd Kennedye9910222017-02-21 16:00:11 -0800320 final IIntentSender successIntentTarget = ActivityManager.getService()
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800321 .getIntentSender(
322 ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
323 null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
Todd Kennedye9910222017-02-21 16:00:11 -0800324 new Intent[] { successIntent },
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800325 new String[] { resolvedType },
326 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
327 | PendingIntent.FLAG_IMMUTABLE,
328 null /*bOptions*/, userId);
Patrick Baumann159cd022018-01-11 13:25:05 -0800329 IntentSender successSender = new IntentSender(successIntentTarget);
330 // TODO(b/72700831): remove populating old extra
331 intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, successSender);
332 intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800333 } catch (RemoteException ignore) { /* ignore; same process */ }
Chad Brubaker06068612017-04-06 09:43:47 -0700334 if (verificationBundle != null) {
335 intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
336 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000337 intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
Patrick Baumann860b8ba2018-01-31 01:33:50 +0000338
Patrick Baumann577d4022018-01-31 16:55:10 +0000339 if (filters != null) {
340 Bundle resolvableFilters[] = new Bundle[filters.size()];
341 for (int i = 0, max = filters.size(); i < max; i++) {
342 Bundle resolvableFilter = new Bundle();
343 AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i);
344 resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP,
345 filter.resolveInfo != null
346 && filter.resolveInfo.shouldLetInstallerDecide());
347 resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName);
348 resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName);
349 resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode);
350 resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras);
351 resolvableFilters[i] = resolvableFilter;
352 if (i == 0) {
353 // for backwards compat, always set the first result on the intent and add
354 // the int version code
355 intent.putExtras(resolvableFilter);
356 intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode);
357 }
358 }
359 intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters);
360 }
361 intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
362 }
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800363 return intent;
364 }
365
Todd Kennedy1fb34042017-03-01 13:56:58 -0800366 private static AuxiliaryResolveInfo filterInstantAppIntent(
367 List<InstantAppResolveInfo> instantAppResolveInfoList,
Todd Kennedy7dd99e32017-05-03 16:12:49 -0700368 Intent origIntent, String resolvedType, int userId, String packageName,
Todd Kennedy1fb34042017-03-01 13:56:58 -0800369 InstantAppDigest digest, String token) {
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800370 final int[] shaPrefix = digest.getDigestPrefix();
371 final byte[][] digestBytes = digest.getDigestBytes();
Patrick Baumann577d4022018-01-31 16:55:10 +0000372 boolean requiresSecondPhase = false;
Patrick Baumann577d4022018-01-31 16:55:10 +0000373 ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
Patrick Baumanna6862c22018-05-03 13:16:01 -0700374 boolean requiresPrefixMatch = origIntent.isWebIntent() || (shaPrefix.length > 0
375 && (origIntent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0);
Patrick Baumann577d4022018-01-31 16:55:10 +0000376 for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
Patrick Baumanna6862c22018-05-03 13:16:01 -0700377 if (requiresPrefixMatch && instantAppResolveInfo.shouldLetInstallerDecide()) {
378 Slog.d(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
379 + " required; ignoring");
Patrick Baumann577d4022018-01-31 16:55:10 +0000380 continue;
381 }
382 byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
383 // Only include matching digests if we have a prefix and we're either dealing with a
Patrick Baumanna6862c22018-05-03 13:16:01 -0700384 // prefixed request or the resolveInfo specifies digest details.
385 if (shaPrefix.length > 0 && (requiresPrefixMatch || filterDigestBytes.length > 0)) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000386 boolean matchFound = false;
387 // Go in reverse order so we match the narrowest scope first.
388 for (int i = shaPrefix.length - 1; i >= 0; --i) {
389 if (Arrays.equals(digestBytes[i], filterDigestBytes)) {
390 matchFound = true;
391 break;
392 }
393 }
394 if (!matchFound) {
Patrick Baumann860b8ba2018-01-31 01:33:50 +0000395 continue;
396 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000397 }
398 // We matched a resolve info; resolve the filters to see if anything matches completely.
399 List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
400 origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
401 if (matchFilters != null) {
402 if (matchFilters.isEmpty()) {
403 requiresSecondPhase = true;
Patrick Baumann860b8ba2018-01-31 01:33:50 +0000404 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000405 if (filters == null) {
406 filters = new ArrayList<>(matchFilters);
407 } else {
408 filters.addAll(matchFilters);
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800409 }
Patrick Baumann3e8bd0f2018-01-08 11:23:38 -0800410 }
Patrick Baumann3e8bd0f2018-01-08 11:23:38 -0800411 }
Patrick Baumann577d4022018-01-31 16:55:10 +0000412 if (filters != null && !filters.isEmpty()) {
Patrick Baumanna6862c22018-05-03 13:16:01 -0700413 return new AuxiliaryResolveInfo(token, requiresSecondPhase,
414 createFailureIntent(origIntent, token), filters);
Patrick Baumann3e0b8ee2018-04-26 12:03:15 -0700415 }
Todd Kennedy1fb34042017-03-01 13:56:58 -0800416 // Hash or filter mis-match; no instant apps for this domain.
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800417 return null;
418 }
Todd Kennedy50d946c12017-03-17 13:55:38 -0700419
Patrick Baumann577d4022018-01-31 16:55:10 +0000420 /**
Patrick Baumanna6862c22018-05-03 13:16:01 -0700421 * Creates a failure intent for the installer to send in the case that the instant app cannot be
422 * launched for any reason.
423 */
424 private static Intent createFailureIntent(Intent origIntent, String token) {
425 final Intent failureIntent = new Intent(origIntent);
426 failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
427 failureIntent.setFlags(failureIntent.getFlags() & ~Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
428 failureIntent.setLaunchToken(token);
429 return failureIntent;
430 }
431
432 /**
Patrick Baumann577d4022018-01-31 16:55:10 +0000433 * Returns one of three states: <p/>
434 * <ul>
435 * <li>{@code null} if there are no matches will not be; resolution is unnecessary.</li>
436 * <li>An empty list signifying that a 2nd phase of resolution is required.</li>
437 * <li>A populated list meaning that matches were found and should be sent directly to the
438 * installer</li>
439 * </ul>
440 *
441 */
442 private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
443 Intent origIntent, String resolvedType, int userId, String packageName, String token,
444 InstantAppResolveInfo instantAppInfo) {
445 if (instantAppInfo.shouldLetInstallerDecide()) {
446 return Collections.singletonList(
447 new AuxiliaryResolveInfo.AuxiliaryFilter(
448 instantAppInfo, null /* splitName */,
449 instantAppInfo.getExtras()));
450 }
451 if (packageName != null
452 && !packageName.equals(instantAppInfo.getPackageName())) {
453 return null;
454 }
455 final List<InstantAppIntentFilter> instantAppFilters =
456 instantAppInfo.getIntentFilters();
457 if (instantAppFilters == null || instantAppFilters.isEmpty()) {
458 // No filters on web intent; no matches, 2nd phase unnecessary.
Patrick Baumann0da85372018-02-02 16:07:35 -0800459 if (origIntent.isWebIntent()) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000460 return null;
461 }
462 // No filters; we need to start phase two
Patrick Baumann43c97a02018-01-31 20:09:03 +0000463 if (DEBUG_INSTANT) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000464 Log.d(TAG, "No app filters; go to phase 2");
465 }
466 return Collections.emptyList();
467 }
Patrick Baumann43c97a02018-01-31 20:09:03 +0000468 final PackageManagerService.InstantAppIntentResolver instantAppResolver =
469 new PackageManagerService.InstantAppIntentResolver();
Patrick Baumann577d4022018-01-31 16:55:10 +0000470 for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
471 final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
472 final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
473 if (splitFilters == null || splitFilters.isEmpty()) {
474 continue;
475 }
476 for (int k = splitFilters.size() - 1; k >= 0; --k) {
477 IntentFilter filter = splitFilters.get(k);
478 Iterator<IntentFilter.AuthorityEntry> authorities =
479 filter.authoritiesIterator();
480 // ignore http/s-only filters.
481 if ((authorities == null || !authorities.hasNext())
482 && (filter.hasDataScheme("http") || filter.hasDataScheme("https"))
483 && filter.hasAction(Intent.ACTION_VIEW)
484 && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
485 continue;
486 }
487 instantAppResolver.addFilter(
488 new AuxiliaryResolveInfo.AuxiliaryFilter(
489 filter,
490 instantAppInfo,
491 instantAppFilter.getSplitName(),
492 instantAppInfo.getExtras()
493 ));
494 }
495 }
496 List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
497 instantAppResolver.queryIntent(
498 origIntent, resolvedType, false /*defaultOnly*/, userId);
499 if (!matchedResolveInfoList.isEmpty()) {
Patrick Baumann43c97a02018-01-31 20:09:03 +0000500 if (DEBUG_INSTANT) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000501 Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
502 }
503 return matchedResolveInfoList;
Patrick Baumann43c97a02018-01-31 20:09:03 +0000504 } else if (DEBUG_INSTANT) {
Patrick Baumann577d4022018-01-31 16:55:10 +0000505 Log.d(TAG, "[" + token + "] No matches found"
506 + " package: " + instantAppInfo.getPackageName()
507 + ", versionCode: " + instantAppInfo.getVersionCode());
508 }
509 return null;
510 }
511
Todd Kennedybdf2a802017-05-08 16:09:42 -0700512 private static void logMetrics(int action, long startTime, String token,
513 @ResolutionStatus int status) {
Todd Kennedy50d946c12017-03-17 13:55:38 -0700514 final LogMaker logMaker = new LogMaker(action)
515 .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
516 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS,
517 new Long(System.currentTimeMillis() - startTime))
518 .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token)
519 .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status));
520 getLogger().write(logMaker);
521 }
Todd Kennedy01ad0c72016-11-11 15:33:12 -0800522}