InstantAppResolver.java revision 159cd024d996047ac8caf1ae941d5be80047dedf
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.IntDef;
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.app.ActivityManager;
29import android.app.PendingIntent;
30import android.content.ComponentName;
31import android.content.Context;
32import android.content.IIntentSender;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.IntentSender;
36import android.content.pm.ActivityInfo;
37import android.content.pm.InstantAppRequest;
38import android.content.pm.AuxiliaryResolveInfo;
39import android.content.pm.InstantAppIntentFilter;
40import android.content.pm.InstantAppResolveInfo;
41import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
42import android.metrics.LogMaker;
43import android.net.Uri;
44import android.os.Build;
45import android.os.Bundle;
46import android.os.Handler;
47import android.os.RemoteException;
48import android.text.TextUtils;
49import android.util.Log;
50import android.util.Slog;
51
52import com.android.internal.logging.MetricsLogger;
53import com.android.internal.logging.nano.MetricsProto;
54import com.android.server.pm.InstantAppResolverConnection.ConnectionException;
55import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback;
56
57import java.lang.annotation.Retention;
58import java.lang.annotation.RetentionPolicy;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.Collections;
62import java.util.Iterator;
63import java.util.List;
64import java.util.Set;
65import java.util.UUID;
66
67/** @hide */
68public abstract class InstantAppResolver {
69    private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
70    private static final String TAG = "PackageManager";
71
72    private static final int RESOLUTION_SUCCESS = 0;
73    private static final int RESOLUTION_FAILURE = 1;
74    /** Binding to the external service timed out */
75    private static final int RESOLUTION_BIND_TIMEOUT = 2;
76    /** The call to retrieve an instant application response timed out */
77    private static final int RESOLUTION_CALL_TIMEOUT = 3;
78
79    @IntDef(flag = true, prefix = { "RESOLUTION_" }, value = {
80            RESOLUTION_SUCCESS,
81            RESOLUTION_FAILURE,
82            RESOLUTION_BIND_TIMEOUT,
83            RESOLUTION_CALL_TIMEOUT,
84    })
85    @Retention(RetentionPolicy.SOURCE)
86    public @interface ResolutionStatus {}
87
88    private static MetricsLogger sMetricsLogger;
89
90    private static MetricsLogger getLogger() {
91        if (sMetricsLogger == null) {
92            sMetricsLogger = new MetricsLogger();
93        }
94        return sMetricsLogger;
95    }
96
97    /**
98     * Returns an intent with potential PII removed from the original intent. Fields removed
99     * include extras and the host + path of the data, if defined.
100     */
101    public static Intent sanitizeIntent(Intent origIntent) {
102        final Intent sanitizedIntent;
103        sanitizedIntent = new Intent(origIntent.getAction());
104        Set<String> categories = origIntent.getCategories();
105        if (categories != null) {
106            for (String category : categories) {
107                sanitizedIntent.addCategory(category);
108            }
109        }
110        Uri sanitizedUri = origIntent.getData() == null
111                ? null
112                : Uri.fromParts(origIntent.getScheme(), "", "");
113        sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType());
114        sanitizedIntent.addFlags(origIntent.getFlags());
115        sanitizedIntent.setPackage(origIntent.getPackage());
116        return sanitizedIntent;
117    }
118
119    public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
120            InstantAppResolverConnection connection, InstantAppRequest requestObj) {
121        final long startTime = System.currentTimeMillis();
122        final String token = UUID.randomUUID().toString();
123        if (DEBUG_INSTANT) {
124            Log.d(TAG, "[" + token + "] Phase1; resolving");
125        }
126        final Intent origIntent = requestObj.origIntent;
127        final Intent sanitizedIntent = sanitizeIntent(origIntent);
128
129        final InstantAppDigest digest = getInstantAppDigest(origIntent);
130        final int[] shaPrefix = digest.getDigestPrefix();
131        AuxiliaryResolveInfo resolveInfo = null;
132        @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
133        try {
134            final List<InstantAppResolveInfo> instantAppResolveInfoList =
135                    connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token);
136            if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
137                resolveInfo = InstantAppResolver.filterInstantAppIntent(
138                        instantAppResolveInfoList, origIntent, requestObj.resolvedType,
139                        requestObj.userId, origIntent.getPackage(), 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        return resolveInfo;
167    }
168
169    private static InstantAppDigest getInstantAppDigest(Intent origIntent) {
170        return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())
171                ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/)
172                : InstantAppDigest.UNDEFINED;
173    }
174
175    public static void doInstantAppResolutionPhaseTwo(Context context,
176            InstantAppResolverConnection connection, InstantAppRequest requestObj,
177            ActivityInfo instantAppInstaller, Handler callbackHandler) {
178        final long startTime = System.currentTimeMillis();
179        final String token = requestObj.responseObj.token;
180        if (DEBUG_INSTANT) {
181            Log.d(TAG, "[" + token + "] Phase2; resolving");
182        }
183        final Intent origIntent = requestObj.origIntent;
184        final Intent sanitizedIntent = sanitizeIntent(origIntent);
185        final InstantAppDigest digest = getInstantAppDigest(origIntent);
186        final int[] shaPrefix = digest.getDigestPrefix();
187
188        final PhaseTwoCallback callback = new PhaseTwoCallback() {
189            @Override
190            void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList,
191                    long startTime) {
192                final Intent failureIntent;
193                if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
194                    final AuxiliaryResolveInfo instantAppIntentInfo =
195                            InstantAppResolver.filterInstantAppIntent(
196                                    instantAppResolveInfoList, origIntent, null /*resolvedType*/,
197                                    0 /*userId*/, origIntent.getPackage(), digest, token);
198                    if (instantAppIntentInfo != null) {
199                        failureIntent = instantAppIntentInfo.failureIntent;
200                    } else {
201                        failureIntent = null;
202                    }
203                } else {
204                    failureIntent = null;
205                }
206                final Intent installerIntent = buildEphemeralInstallerIntent(
207                        requestObj.origIntent,
208                        sanitizedIntent,
209                        failureIntent,
210                        requestObj.callingPackage,
211                        requestObj.verificationBundle,
212                        requestObj.resolvedType,
213                        requestObj.userId,
214                        requestObj.responseObj.installFailureActivity,
215                        token,
216                        false /*needsPhaseTwo*/,
217                        requestObj.responseObj.filters);
218                installerIntent.setComponent(new ComponentName(
219                        instantAppInstaller.packageName, instantAppInstaller.name));
220
221                logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
222                        requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
223
224                context.startActivity(installerIntent);
225            }
226        };
227        try {
228            connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback,
229                    callbackHandler, startTime);
230        } catch (ConnectionException e) {
231            @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
232            if (e.failure == ConnectionException.FAILURE_BIND) {
233                resolutionStatus = RESOLUTION_BIND_TIMEOUT;
234            }
235            logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
236                    resolutionStatus);
237            if (DEBUG_INSTANT) {
238                if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
239                    Log.d(TAG, "[" + token + "] Phase2; bind timed out");
240                } else {
241                    Log.d(TAG, "[" + token + "] Phase2; service connection error");
242                }
243            }
244        }
245    }
246
247    /**
248     * Builds and returns an intent to launch the instant installer.
249     */
250    public static Intent buildEphemeralInstallerIntent(
251            @NonNull Intent origIntent,
252            @NonNull Intent sanitizedIntent,
253            @Nullable Intent failureIntent,
254            @NonNull String callingPackage,
255            @Nullable Bundle verificationBundle,
256            @NonNull String resolvedType,
257            int userId,
258            @Nullable ComponentName installFailureActivity,
259            @Nullable String token,
260            boolean needsPhaseTwo,
261            List<AuxiliaryResolveInfo.AuxiliaryFilter> filters) {
262        // Construct the intent that launches the instant installer
263        int flags = origIntent.getFlags();
264        final Intent intent = new Intent();
265        intent.setFlags(flags
266                | Intent.FLAG_ACTIVITY_NEW_TASK
267                | Intent.FLAG_ACTIVITY_CLEAR_TASK
268                | Intent.FLAG_ACTIVITY_NO_HISTORY
269                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
270        if (token != null) {
271            // TODO(b/72700831): remove populating old extra
272            intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token);
273            intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token);
274        }
275        if (origIntent.getData() != null) {
276            // TODO(b/72700831): remove populating old extra
277            intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
278            intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost());
279        }
280        intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
281        intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
282
283        if (needsPhaseTwo) {
284            intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE);
285        } else {
286            // We have all of the data we need; just start the installer without a second phase
287            if (failureIntent != null || installFailureActivity != null) {
288                // Intent that is launched if the package couldn't be installed for any reason.
289                try {
290                    final Intent onFailureIntent;
291                    if (installFailureActivity != null) {
292                        onFailureIntent = new Intent();
293                        onFailureIntent.setComponent(installFailureActivity);
294                        if (filters != null && filters.size() == 1) {
295                            onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
296                                    filters.get(0).splitName);
297                        }
298                        onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
299                    } else {
300                        onFailureIntent = failureIntent;
301                    }
302                    final IIntentSender failureIntentTarget = ActivityManager.getService()
303                            .getIntentSender(
304                                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
305                                    null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
306                                    new Intent[] { onFailureIntent },
307                                    new String[] { resolvedType },
308                                    PendingIntent.FLAG_CANCEL_CURRENT
309                                            | PendingIntent.FLAG_ONE_SHOT
310                                            | PendingIntent.FLAG_IMMUTABLE,
311                                    null /*bOptions*/, userId);
312                    IntentSender failureSender = new IntentSender(failureIntentTarget);
313                    // TODO(b/72700831): remove populating old extra
314                    intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, failureSender);
315                    intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender);
316                } catch (RemoteException ignore) { /* ignore; same process */ }
317            }
318
319            // Intent that is launched if the package was installed successfully.
320            final Intent successIntent = new Intent(origIntent);
321            successIntent.setLaunchToken(token);
322            try {
323                final IIntentSender successIntentTarget = ActivityManager.getService()
324                        .getIntentSender(
325                                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
326                                null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
327                                new Intent[] { successIntent },
328                                new String[] { resolvedType },
329                                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
330                                        | PendingIntent.FLAG_IMMUTABLE,
331                                null /*bOptions*/, userId);
332                IntentSender successSender = new IntentSender(successIntentTarget);
333                // TODO(b/72700831): remove populating old extra
334                intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, successSender);
335                intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender);
336            } catch (RemoteException ignore) { /* ignore; same process */ }
337            if (verificationBundle != null) {
338                intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
339            }
340            intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
341
342            if (filters != null) {
343                Bundle resolvableFilters[] = new Bundle[filters.size()];
344                for (int i = 0, max = filters.size(); i < max; i++) {
345                    Bundle resolvableFilter = new Bundle();
346                    AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i);
347                    resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP,
348                            filter.resolveInfo != null
349                                    && filter.resolveInfo.shouldLetInstallerDecide());
350                    resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName);
351                    resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName);
352                    resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode);
353                    resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras);
354                    resolvableFilters[i] = resolvableFilter;
355                    if (i == 0) {
356                        // for backwards compat, always set the first result on the intent and add
357                        // the int version code
358                        intent.putExtras(resolvableFilter);
359                        intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode);
360                    }
361                }
362                intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters);
363            }
364            intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
365        }
366        return intent;
367    }
368
369    private static AuxiliaryResolveInfo filterInstantAppIntent(
370            List<InstantAppResolveInfo> instantAppResolveInfoList,
371            Intent origIntent, String resolvedType, int userId, String packageName,
372            InstantAppDigest digest, String token) {
373        final int[] shaPrefix = digest.getDigestPrefix();
374        final byte[][] digestBytes = digest.getDigestBytes();
375        final Intent failureIntent = new Intent(origIntent);
376        boolean requiresSecondPhase = false;
377        failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
378        failureIntent.setLaunchToken(token);
379        ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
380        boolean isWebIntent = origIntent.isBrowsableWebIntent();
381        for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
382            if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) {
383                Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
384                        + " provided; ignoring");
385                continue;
386            }
387            byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
388            // Only include matching digests if we have a prefix and we're either dealing with a
389            // web intent or the resolveInfo specifies digest details.
390            if (shaPrefix.length > 0 && (isWebIntent || filterDigestBytes.length > 0)) {
391                boolean matchFound = false;
392                // Go in reverse order so we match the narrowest scope first.
393                for (int i = shaPrefix.length - 1; i >= 0; --i) {
394                    if (Arrays.equals(digestBytes[i], filterDigestBytes)) {
395                        matchFound = true;
396                        break;
397                    }
398                }
399                if (!matchFound) {
400                    continue;
401                }
402            }
403            // We matched a resolve info; resolve the filters to see if anything matches completely.
404            List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
405                    origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
406            if (matchFilters != null) {
407                if (matchFilters.isEmpty()) {
408                    requiresSecondPhase = true;
409                }
410                if (filters == null) {
411                    filters = new ArrayList<>(matchFilters);
412                } else {
413                    filters.addAll(matchFilters);
414                }
415            }
416        }
417        if (filters != null && !filters.isEmpty()) {
418            return new AuxiliaryResolveInfo(token, requiresSecondPhase, failureIntent, filters);
419        }
420        // Hash or filter mis-match; no instant apps for this domain.
421        return null;
422    }
423
424    /**
425     * Returns one of three states: <p/>
426     * <ul>
427     *     <li>{@code null} if there are no matches will not be; resolution is unnecessary.</li>
428     *     <li>An empty list signifying that a 2nd phase of resolution is required.</li>
429     *     <li>A populated list meaning that matches were found and should be sent directly to the
430     *     installer</li>
431     * </ul>
432     *
433     */
434    private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
435            Intent origIntent, String resolvedType, int userId, String packageName, String token,
436            InstantAppResolveInfo instantAppInfo) {
437        if (instantAppInfo.shouldLetInstallerDecide()) {
438            return Collections.singletonList(
439                    new AuxiliaryResolveInfo.AuxiliaryFilter(
440                            instantAppInfo, null /* splitName */,
441                            instantAppInfo.getExtras()));
442        }
443        if (packageName != null
444                && !packageName.equals(instantAppInfo.getPackageName())) {
445            return null;
446        }
447        final List<InstantAppIntentFilter> instantAppFilters =
448                instantAppInfo.getIntentFilters();
449        if (instantAppFilters == null || instantAppFilters.isEmpty()) {
450            // No filters on web intent; no matches, 2nd phase unnecessary.
451            if (origIntent.isBrowsableWebIntent()) {
452                return null;
453            }
454            // No filters; we need to start phase two
455            if (DEBUG_INSTANT) {
456                Log.d(TAG, "No app filters; go to phase 2");
457            }
458            return Collections.emptyList();
459        }
460        final PackageManagerService.InstantAppIntentResolver instantAppResolver =
461                new PackageManagerService.InstantAppIntentResolver();
462        for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
463            final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
464            final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
465            if (splitFilters == null || splitFilters.isEmpty()) {
466                continue;
467            }
468            for (int k = splitFilters.size() - 1; k >= 0; --k) {
469                IntentFilter filter = splitFilters.get(k);
470                Iterator<IntentFilter.AuthorityEntry> authorities =
471                        filter.authoritiesIterator();
472                // ignore http/s-only filters.
473                if ((authorities == null || !authorities.hasNext())
474                        && (filter.hasDataScheme("http") || filter.hasDataScheme("https"))
475                        && filter.hasAction(Intent.ACTION_VIEW)
476                        && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
477                    continue;
478                }
479                instantAppResolver.addFilter(
480                        new AuxiliaryResolveInfo.AuxiliaryFilter(
481                                filter,
482                                instantAppInfo,
483                                instantAppFilter.getSplitName(),
484                                instantAppInfo.getExtras()
485                        ));
486            }
487        }
488        List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
489                instantAppResolver.queryIntent(
490                        origIntent, resolvedType, false /*defaultOnly*/, userId);
491        if (!matchedResolveInfoList.isEmpty()) {
492            if (DEBUG_INSTANT) {
493                Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
494            }
495            return matchedResolveInfoList;
496        } else if (DEBUG_INSTANT) {
497            Log.d(TAG, "[" + token + "] No matches found"
498                    + " package: " + instantAppInfo.getPackageName()
499                    + ", versionCode: " + instantAppInfo.getVersionCode());
500        }
501        return null;
502    }
503
504    private static void logMetrics(int action, long startTime, String token,
505            @ResolutionStatus int status) {
506        final LogMaker logMaker = new LogMaker(action)
507                .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
508                .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS,
509                        new Long(System.currentTimeMillis() - startTime))
510                .addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN, token)
511                .addTaggedData(FIELD_INSTANT_APP_RESOLUTION_STATUS, new Integer(status));
512        getLogger().write(logMaker);
513    }
514}
515