InstantAppResolver.java revision a6862c238039cba1b2f6175beca58f8d76e1bef9
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 android.content.Intent.FLAG_ACTIVITY_MATCH_EXTERNAL;
20
21import 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
27import android.annotation.IntDef;
28import android.annotation.NonNull;
29import android.annotation.Nullable;
30import android.app.ActivityManager;
31import 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;
39import android.content.pm.InstantAppRequest;
40import android.content.pm.AuxiliaryResolveInfo;
41import android.content.pm.InstantAppIntentFilter;
42import android.content.pm.InstantAppResolveInfo;
43import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
44import android.metrics.LogMaker;
45import android.net.Uri;
46import android.os.Build;
47import android.os.Bundle;
48import android.os.Handler;
49import android.os.RemoteException;
50import android.util.Log;
51import android.util.Slog;
52
53import com.android.internal.logging.MetricsLogger;
54import com.android.internal.logging.nano.MetricsProto;
55import com.android.server.pm.InstantAppResolverConnection.ConnectionException;
56import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback;
57
58import java.lang.annotation.Retention;
59import java.lang.annotation.RetentionPolicy;
60import java.util.ArrayList;
61import java.util.Arrays;
62import java.util.Collections;
63import java.util.Iterator;
64import java.util.List;
65import java.util.Set;
66import java.util.UUID;
67
68/** @hide */
69public abstract class InstantAppResolver {
70    private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
71    private static final String TAG = "PackageManager";
72
73    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 {}
88
89    private static MetricsLogger sMetricsLogger;
90
91    private static MetricsLogger getLogger() {
92        if (sMetricsLogger == null) {
93            sMetricsLogger = new MetricsLogger();
94        }
95        return sMetricsLogger;
96    }
97
98    /**
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(
121            InstantAppResolverConnection connection, InstantAppRequest requestObj) {
122        final long startTime = System.currentTimeMillis();
123        final String token = UUID.randomUUID().toString();
124        if (DEBUG_INSTANT) {
125            Log.d(TAG, "[" + token + "] Phase1; resolving");
126        }
127        final Intent origIntent = requestObj.origIntent;
128        final Intent sanitizedIntent = sanitizeIntent(origIntent);
129
130        AuxiliaryResolveInfo resolveInfo = null;
131        @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
132        try {
133            final List<InstantAppResolveInfo> instantAppResolveInfoList =
134                    connection.getInstantAppResolveInfoList(sanitizedIntent,
135                            requestObj.digest.getDigestPrefixSecure(), token);
136            if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
137                resolveInfo = InstantAppResolver.filterInstantAppIntent(
138                        instantAppResolveInfoList, origIntent, requestObj.resolvedType,
139                        requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
140            }
141        } 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            }
149        }
150        // Only log successful instant application resolution
151        if (requestObj.resolveForStart && resolutionStatus == RESOLUTION_SUCCESS) {
152            logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
153                    resolutionStatus);
154        }
155        if (DEBUG_INSTANT && resolveInfo == null) {
156            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            }
165        }
166        // 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        }
171        return resolveInfo;
172    }
173
174    public static void doInstantAppResolutionPhaseTwo(Context context,
175            InstantAppResolverConnection connection, InstantAppRequest requestObj,
176            ActivityInfo instantAppInstaller, Handler callbackHandler) {
177        final long startTime = System.currentTimeMillis();
178        final String token = requestObj.responseObj.token;
179        if (DEBUG_INSTANT) {
180            Log.d(TAG, "[" + token + "] Phase2; resolving");
181        }
182        final Intent origIntent = requestObj.origIntent;
183        final Intent sanitizedIntent = sanitizeIntent(origIntent);
184
185        final PhaseTwoCallback callback = new PhaseTwoCallback() {
186            @Override
187            void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList,
188                    long startTime) {
189                final Intent failureIntent;
190                if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
191                    final AuxiliaryResolveInfo instantAppIntentInfo =
192                            InstantAppResolver.filterInstantAppIntent(
193                                    instantAppResolveInfoList, origIntent, null /*resolvedType*/,
194                                    0 /*userId*/, origIntent.getPackage(), requestObj.digest,
195                                    token);
196                    if (instantAppIntentInfo != null) {
197                        failureIntent = instantAppIntentInfo.failureIntent;
198                    } else {
199                        failureIntent = null;
200                    }
201                } else {
202                    failureIntent = null;
203                }
204                final Intent installerIntent = buildEphemeralInstallerIntent(
205                        requestObj.origIntent,
206                        sanitizedIntent,
207                        failureIntent,
208                        requestObj.callingPackage,
209                        requestObj.verificationBundle,
210                        requestObj.resolvedType,
211                        requestObj.userId,
212                        requestObj.responseObj.installFailureActivity,
213                        token,
214                        false /*needsPhaseTwo*/,
215                        requestObj.responseObj.filters);
216                installerIntent.setComponent(new ComponentName(
217                        instantAppInstaller.packageName, instantAppInstaller.name));
218
219                logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
220                        requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
221
222                context.startActivity(installerIntent);
223            }
224        };
225        try {
226            connection.getInstantAppIntentFilterList(sanitizedIntent,
227                    requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler,
228                    startTime);
229        } 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);
236            if (DEBUG_INSTANT) {
237                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        }
244    }
245
246    /**
247     * Builds and returns an intent to launch the instant installer.
248     */
249    public static Intent buildEphemeralInstallerIntent(
250            @NonNull Intent origIntent,
251            @NonNull Intent sanitizedIntent,
252            @Nullable Intent failureIntent,
253            @NonNull String callingPackage,
254            @Nullable Bundle verificationBundle,
255            @NonNull String resolvedType,
256            int userId,
257            @Nullable ComponentName installFailureActivity,
258            @Nullable String token,
259            boolean needsPhaseTwo,
260            List<AuxiliaryResolveInfo.AuxiliaryFilter> filters) {
261        // Construct the intent that launches the instant installer
262        int flags = origIntent.getFlags();
263        final Intent intent = new Intent();
264        intent.setFlags(flags
265                | Intent.FLAG_ACTIVITY_NO_HISTORY
266                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
267        if (token != null) {
268            // TODO(b/72700831): remove populating old extra
269            intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token);
270            intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token);
271        }
272        if (origIntent.getData() != null) {
273            // TODO(b/72700831): remove populating old extra
274            intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
275            intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost());
276        }
277        intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
278        intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
279
280        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
284            if (failureIntent != null || installFailureActivity != null) {
285                // Intent that is launched if the package couldn't be installed for any reason.
286                try {
287                    final Intent onFailureIntent;
288                    if (installFailureActivity != null) {
289                        onFailureIntent = new Intent();
290                        onFailureIntent.setComponent(installFailureActivity);
291                        if (filters != null && filters.size() == 1) {
292                            onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
293                                    filters.get(0).splitName);
294                        }
295                        onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
296                    } else {
297                        onFailureIntent = failureIntent;
298                    }
299                    final IIntentSender failureIntentTarget = ActivityManager.getService()
300                            .getIntentSender(
301                                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
302                                    null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
303                                    new Intent[] { onFailureIntent },
304                                    new String[] { resolvedType },
305                                    PendingIntent.FLAG_CANCEL_CURRENT
306                                            | PendingIntent.FLAG_ONE_SHOT
307                                            | PendingIntent.FLAG_IMMUTABLE,
308                                    null /*bOptions*/, userId);
309                    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);
313                } catch (RemoteException ignore) { /* ignore; same process */ }
314            }
315
316            // Intent that is launched if the package was installed successfully.
317            final Intent successIntent = new Intent(origIntent);
318            successIntent.setLaunchToken(token);
319            try {
320                final IIntentSender successIntentTarget = ActivityManager.getService()
321                        .getIntentSender(
322                                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
323                                null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
324                                new Intent[] { successIntent },
325                                new String[] { resolvedType },
326                                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
327                                        | PendingIntent.FLAG_IMMUTABLE,
328                                null /*bOptions*/, userId);
329                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);
333            } catch (RemoteException ignore) { /* ignore; same process */ }
334            if (verificationBundle != null) {
335                intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
336            }
337            intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
338
339            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        }
363        return intent;
364    }
365
366    private static AuxiliaryResolveInfo filterInstantAppIntent(
367            List<InstantAppResolveInfo> instantAppResolveInfoList,
368            Intent origIntent, String resolvedType, int userId, String packageName,
369            InstantAppDigest digest, String token) {
370        final int[] shaPrefix = digest.getDigestPrefix();
371        final byte[][] digestBytes = digest.getDigestBytes();
372        boolean requiresSecondPhase = false;
373        ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
374        boolean requiresPrefixMatch = origIntent.isWebIntent() || (shaPrefix.length > 0
375                        && (origIntent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0);
376        for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
377            if (requiresPrefixMatch && instantAppResolveInfo.shouldLetInstallerDecide()) {
378                Slog.d(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
379                        + " required; ignoring");
380                continue;
381            }
382            byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
383            // Only include matching digests if we have a prefix and we're either dealing with a
384            // prefixed request or the resolveInfo specifies digest details.
385            if (shaPrefix.length > 0 && (requiresPrefixMatch || filterDigestBytes.length > 0)) {
386                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) {
395                    continue;
396                }
397            }
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;
404                }
405                if (filters == null) {
406                    filters = new ArrayList<>(matchFilters);
407                } else {
408                    filters.addAll(matchFilters);
409                }
410            }
411        }
412        if (filters != null && !filters.isEmpty()) {
413            return new AuxiliaryResolveInfo(token, requiresSecondPhase,
414                    createFailureIntent(origIntent, token), filters);
415        }
416        // Hash or filter mis-match; no instant apps for this domain.
417        return null;
418    }
419
420    /**
421     * 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    /**
433     * 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.
459            if (origIntent.isWebIntent()) {
460                return null;
461            }
462            // No filters; we need to start phase two
463            if (DEBUG_INSTANT) {
464                Log.d(TAG, "No app filters; go to phase 2");
465            }
466            return Collections.emptyList();
467        }
468        final PackageManagerService.InstantAppIntentResolver instantAppResolver =
469                new PackageManagerService.InstantAppIntentResolver();
470        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()) {
500            if (DEBUG_INSTANT) {
501                Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
502            }
503            return matchedResolveInfoList;
504        } else if (DEBUG_INSTANT) {
505            Log.d(TAG, "[" + token + "] No matches found"
506                    + " package: " + instantAppInfo.getPackageName()
507                    + ", versionCode: " + instantAppInfo.getVersionCode());
508        }
509        return null;
510    }
511
512    private static void logMetrics(int action, long startTime, String token,
513            @ResolutionStatus int status) {
514        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    }
522}
523