AutofillManagerServiceImpl.java revision 1116652087c5889a98ff6d736e127fc0e97dbe37
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.autofill;
18
19import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
21import static android.view.autofill.AutofillManager.NO_SESSION;
22
23import static com.android.server.autofill.Helper.sDebug;
24import static com.android.server.autofill.Helper.sVerbose;
25
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.app.ActivityManager;
29import android.app.ActivityManagerInternal;
30import android.app.AppGlobals;
31import android.app.IActivityManager;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.pm.ApplicationInfo;
35import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.ServiceInfo;
38import android.graphics.Rect;
39import android.graphics.drawable.Drawable;
40import android.metrics.LogMaker;
41import android.os.AsyncTask;
42import android.os.Binder;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Looper;
47import android.os.RemoteCallbackList;
48import android.os.RemoteException;
49import android.os.SystemClock;
50import android.os.UserHandle;
51import android.os.UserManager;
52import android.provider.Settings;
53import android.service.autofill.AutofillService;
54import android.service.autofill.AutofillServiceInfo;
55import android.service.autofill.FieldClassification;
56import android.service.autofill.FieldClassification.Match;
57import android.service.autofill.FillEventHistory;
58import android.service.autofill.FillEventHistory.Event;
59import android.service.autofill.FillResponse;
60import android.service.autofill.IAutoFillService;
61import android.service.autofill.UserData;
62import android.text.TextUtils;
63import android.util.ArrayMap;
64import android.util.ArraySet;
65import android.util.DebugUtils;
66import android.util.LocalLog;
67import android.util.Slog;
68import android.util.SparseArray;
69import android.util.TimeUtils;
70import android.view.autofill.AutofillId;
71import android.view.autofill.AutofillManager;
72import android.view.autofill.AutofillValue;
73import android.view.autofill.IAutoFillManagerClient;
74
75import com.android.internal.R;
76import com.android.internal.annotations.GuardedBy;
77import com.android.internal.logging.MetricsLogger;
78import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
79import com.android.server.LocalServices;
80import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
81import com.android.server.autofill.ui.AutoFillUI;
82
83import java.io.PrintWriter;
84import java.util.ArrayList;
85import java.util.List;
86import java.util.Random;
87
88/**
89 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
90 * app's {@link IAutoFillService} implementation.
91 *
92 */
93final class AutofillManagerServiceImpl {
94
95    private static final String TAG = "AutofillManagerServiceImpl";
96    private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
97
98    /** Minimum interval to prune abandoned sessions */
99    private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
100
101    private final int mUserId;
102    private final Context mContext;
103    private final Object mLock;
104    private final AutoFillUI mUi;
105    private final MetricsLogger mMetricsLogger = new MetricsLogger();
106
107    @GuardedBy("mLock")
108    private RemoteCallbackList<IAutoFillManagerClient> mClients;
109
110    @GuardedBy("mLock")
111    private AutofillServiceInfo mInfo;
112
113    private static final Random sRandom = new Random();
114
115    private final LocalLog mRequestsHistory;
116    private final LocalLog mUiLatencyHistory;
117    private final LocalLog mWtfHistory;
118    private final FieldClassificationStrategy mFieldClassificationStrategy;
119
120    /**
121     * Apps disabled by the service; key is package name, value is when they will be enabled again.
122     */
123    @GuardedBy("mLock")
124    private ArrayMap<String, Long> mDisabledApps;
125
126    /**
127     * Activities disabled by the service; key is component name, value is when they will be enabled
128     * again.
129     */
130    @GuardedBy("mLock")
131    private ArrayMap<ComponentName, Long> mDisabledActivities;
132
133    /**
134     * Whether service was disabled for user due to {@link UserManager} restrictions.
135     */
136    @GuardedBy("mLock")
137    private boolean mDisabled;
138
139    /**
140     * Data used for field classification.
141     */
142    @GuardedBy("mLock")
143    private UserData mUserData;
144
145    /**
146     * Caches whether the setup completed for the current user.
147     */
148    @GuardedBy("mLock")
149    private boolean mSetupComplete;
150
151    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
152
153    /**
154     * Cache of pending {@link Session}s, keyed by sessionId.
155     *
156     * <p>They're kept until the {@link AutofillService} finished handling a request, an error
157     * occurs, or the session is abandoned.
158     */
159    @GuardedBy("mLock")
160    private final SparseArray<Session> mSessions = new SparseArray<>();
161
162    /** The last selection */
163    @GuardedBy("mLock")
164    private FillEventHistory mEventHistory;
165
166    /** Shared instance, doesn't need to be logged */
167    private final AutofillCompatState mAutofillCompatState;
168
169    /** When was {@link PruneTask} last executed? */
170    private long mLastPrune = 0;
171
172    AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
173            LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
174            AutofillCompatState autofillCompatState, boolean disabled) {
175        mContext = context;
176        mLock = lock;
177        mRequestsHistory = requestsHistory;
178        mUiLatencyHistory = uiLatencyHistory;
179        mWtfHistory = wtfHistory;
180        mUserId = userId;
181        mUi = ui;
182        mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
183        mAutofillCompatState = autofillCompatState;
184        updateLocked(disabled);
185    }
186
187    @Nullable
188    CharSequence getServiceName() {
189        final String packageName = getServicePackageName();
190        if (packageName == null) {
191            return null;
192        }
193
194        try {
195            final PackageManager pm = mContext.getPackageManager();
196            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
197            return pm.getApplicationLabel(info);
198        } catch (Exception e) {
199            Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
200            return packageName;
201        }
202    }
203
204    @GuardedBy("mLock")
205    private int getServiceUidLocked() {
206        if (mInfo == null) {
207            Slog.w(TAG,  "getServiceUidLocked(): no mInfo");
208            return -1;
209        }
210        return mInfo.getServiceInfo().applicationInfo.uid;
211    }
212
213
214    @Nullable
215    String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) {
216        return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId);
217    }
218
219    @Nullable
220    String getServicePackageName() {
221        final ComponentName serviceComponent = getServiceComponentName();
222        if (serviceComponent != null) {
223            return serviceComponent.getPackageName();
224        }
225        return null;
226    }
227
228    ComponentName getServiceComponentName() {
229        synchronized (mLock) {
230            if (mInfo == null) {
231                return null;
232            }
233            return mInfo.getServiceInfo().getComponentName();
234        }
235    }
236
237    private boolean isSetupCompletedLocked() {
238        final String setupComplete = Settings.Secure.getStringForUser(
239                mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
240        return "1".equals(setupComplete);
241    }
242
243    private String getComponentNameFromSettings() {
244        return Settings.Secure.getStringForUser(
245                mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
246    }
247
248    @GuardedBy("mLock")
249    void updateLocked(boolean disabled) {
250        final boolean wasEnabled = isEnabledLocked();
251        if (sVerbose) {
252            Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
253                    + ", mSetupComplete= " + mSetupComplete
254                    + ", disabled=" + disabled + ", mDisabled=" + mDisabled);
255        }
256        mSetupComplete = isSetupCompletedLocked();
257        mDisabled = disabled;
258        ComponentName serviceComponent = null;
259        ServiceInfo serviceInfo = null;
260        final String componentName = getComponentNameFromSettings();
261        if (!TextUtils.isEmpty(componentName)) {
262            try {
263                serviceComponent = ComponentName.unflattenFromString(componentName);
264                serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
265                        0, mUserId);
266                if (serviceInfo == null) {
267                    Slog.e(TAG, "Bad AutofillService name: " + componentName);
268                }
269            } catch (RuntimeException | RemoteException e) {
270                Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e);
271                serviceInfo = null;
272            }
273        }
274        try {
275            if (serviceInfo != null) {
276                mInfo = new AutofillServiceInfo(mContext, serviceComponent, mUserId);
277                if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
278            } else {
279                mInfo = null;
280                if (sDebug) {
281                    Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")");
282                }
283            }
284        } catch (Exception e) {
285            Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e);
286            mInfo = null;
287        }
288        final boolean isEnabled = isEnabledLocked();
289        if (wasEnabled != isEnabled) {
290            if (!isEnabled) {
291                final int sessionCount = mSessions.size();
292                for (int i = sessionCount - 1; i >= 0; i--) {
293                    final Session session = mSessions.valueAt(i);
294                    session.removeSelfLocked();
295                }
296            }
297            sendStateToClients(false);
298        }
299    }
300
301    @GuardedBy("mLock")
302    boolean addClientLocked(IAutoFillManagerClient client) {
303        if (mClients == null) {
304            mClients = new RemoteCallbackList<>();
305        }
306        mClients.register(client);
307        return isEnabledLocked();
308    }
309
310    @GuardedBy("mLock")
311    void removeClientLocked(IAutoFillManagerClient client) {
312        if (mClients != null) {
313            mClients.unregister(client);
314        }
315    }
316
317    @GuardedBy("mLock")
318    void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
319        if (!isEnabledLocked()) {
320            return;
321        }
322        final Session session = mSessions.get(sessionId);
323        if (session != null && uid == session.uid) {
324            session.setAuthenticationResultLocked(data, authenticationId);
325        }
326    }
327
328    void setHasCallback(int sessionId, int uid, boolean hasIt) {
329        if (!isEnabledLocked()) {
330            return;
331        }
332        final Session session = mSessions.get(sessionId);
333        if (session != null && uid == session.uid) {
334            synchronized (mLock) {
335                session.setHasCallbackLocked(hasIt);
336            }
337        }
338    }
339
340    @GuardedBy("mLock")
341    int startSessionLocked(@NonNull IBinder activityToken, int uid,
342            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
343            @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
344            int flags, @NonNull ComponentName componentName, boolean compatMode) {
345        if (!isEnabledLocked()) {
346            return 0;
347        }
348
349        final String shortComponentName = componentName.toShortString();
350
351        if (isAutofillDisabledLocked(componentName)) {
352            if (sDebug) {
353                Slog.d(TAG, "startSession(" + shortComponentName
354                        + "): ignored because disabled by service");
355            }
356
357            final IAutoFillManagerClient client = IAutoFillManagerClient.Stub
358                    .asInterface(appCallbackToken);
359            try {
360                client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE);
361            } catch (RemoteException e) {
362                Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e);
363            }
364
365            return NO_SESSION;
366        }
367
368        if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
369
370        // Occasionally clean up abandoned sessions
371        pruneAbandonedSessionsLocked();
372
373        final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
374                hasCallback, componentName, compatMode, flags);
375        if (newSession == null) {
376            return NO_SESSION;
377        }
378
379        final String historyItem =
380                "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName
381                + " s=" + mInfo.getServiceInfo().packageName
382                + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds
383                + " hc=" + hasCallback + " f=" + flags;
384        mRequestsHistory.log(historyItem);
385
386        newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
387
388        return newSession.id;
389    }
390
391    /**
392     * Remove abandoned sessions if needed.
393     */
394    @GuardedBy("mLock")
395    private void pruneAbandonedSessionsLocked() {
396        long now = System.currentTimeMillis();
397        if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
398            mLastPrune = now;
399
400            if (mSessions.size() > 0) {
401                (new PruneTask()).execute();
402            }
403        }
404    }
405
406    @GuardedBy("mLock")
407    void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
408        if (!isEnabledLocked()) {
409            return;
410        }
411        final Session session = mSessions.get(sessionId);
412        if (session == null || uid != session.uid) {
413            Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")");
414            return;
415        }
416        session.setAutofillFailureLocked(ids);
417    }
418
419    @GuardedBy("mLock")
420    void finishSessionLocked(int sessionId, int uid) {
421        if (!isEnabledLocked()) {
422            return;
423        }
424
425        final Session session = mSessions.get(sessionId);
426        if (session == null || uid != session.uid) {
427            if (sVerbose) {
428                Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")");
429            }
430            return;
431        }
432
433        session.logContextCommitted();
434
435        final boolean finished = session.showSaveLocked();
436        if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
437
438        if (finished) {
439            session.removeSelfLocked();
440        }
441    }
442
443    @GuardedBy("mLock")
444    void cancelSessionLocked(int sessionId, int uid) {
445        if (!isEnabledLocked()) {
446            return;
447        }
448
449        final Session session = mSessions.get(sessionId);
450        if (session == null || uid != session.uid) {
451            Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
452            return;
453        }
454        session.removeSelfLocked();
455    }
456
457    @GuardedBy("mLock")
458    void disableOwnedAutofillServicesLocked(int uid) {
459        Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
460        if (mInfo == null) return;
461
462        final ServiceInfo serviceInfo = mInfo.getServiceInfo();
463        if (serviceInfo.applicationInfo.uid != uid) {
464            Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
465                    + " instead of " + serviceInfo.applicationInfo.uid
466                    + " for service " + mInfo);
467            return;
468        }
469
470
471        final long identity = Binder.clearCallingIdentity();
472        try {
473            final String autoFillService = getComponentNameFromSettings();
474            final ComponentName componentName = serviceInfo.getComponentName();
475            if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
476                mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
477                        componentName.getPackageName());
478                Settings.Secure.putStringForUser(mContext.getContentResolver(),
479                        Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
480                destroySessionsLocked();
481            } else {
482                Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
483                        + serviceInfo + ") does not match Settings (" + autoFillService + ")");
484            }
485        } finally {
486            Binder.restoreCallingIdentity(identity);
487        }
488    }
489
490    @GuardedBy("mLock")
491    private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
492            @NonNull IBinder appCallbackToken, boolean hasCallback,
493            @NonNull ComponentName componentName, boolean compatMode, int flags) {
494        // use random ids so that one app cannot know that another app creates sessions
495        int sessionId;
496        int tries = 0;
497        do {
498            tries++;
499            if (tries > MAX_SESSION_ID_CREATE_TRIES) {
500                Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
501                return null;
502            }
503
504            sessionId = sRandom.nextInt();
505        } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
506
507        assertCallerLocked(componentName, compatMode);
508
509        final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock,
510                sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory,
511                mWtfHistory, mInfo.getServiceInfo().getComponentName(), componentName, compatMode,
512                flags);
513        mSessions.put(newSession.id, newSession);
514
515        return newSession;
516    }
517
518    /**
519     * Asserts the component is owned by the caller.
520     */
521    private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) {
522        final String packageName = componentName.getPackageName();
523        final PackageManager pm = mContext.getPackageManager();
524        final int callingUid = Binder.getCallingUid();
525        final int packageUid;
526        try {
527            packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
528        } catch (NameNotFoundException e) {
529            throw new SecurityException("Could not verify UID for " + componentName);
530        }
531        if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
532                .hasRunningActivity(callingUid, packageName)) {
533            final String[] packages = pm.getPackagesForUid(callingUid);
534            final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
535            Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
536                    + ") passed component (" + componentName + ") owned by UID " + packageUid);
537            mMetricsLogger.write(
538                    Helper.newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT,
539                            callingPackage, getServicePackageName(), compatMode)
540                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
541                            componentName == null ? "null" : componentName.flattenToShortString()));
542
543            throw new SecurityException("Invalid component: " + componentName);
544        }
545    }
546
547    /**
548     * Restores a session after an activity was temporarily destroyed.
549     *
550     * @param sessionId The id of the session to restore
551     * @param uid UID of the process that tries to restore the session
552     * @param activityToken The new instance of the activity
553     * @param appCallback The callbacks to the activity
554     */
555    boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
556            @NonNull IBinder appCallback) {
557        final Session session = mSessions.get(sessionId);
558
559        if (session == null || uid != session.uid) {
560            return false;
561        } else {
562            session.switchActivity(activityToken, appCallback);
563            return true;
564        }
565    }
566
567    /**
568     * Updates a session and returns whether it should be restarted.
569     */
570    @GuardedBy("mLock")
571    boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
572            AutofillValue value, int action, int flags) {
573        final Session session = mSessions.get(sessionId);
574        if (session == null || session.uid != uid) {
575            if ((flags & FLAG_MANUAL_REQUEST) != 0) {
576                if (sDebug) {
577                    Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
578                            + autofillId);
579                }
580                return true;
581            }
582            if (sVerbose) {
583                Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
584                        + "(" + uid + ")");
585            }
586            return false;
587        }
588
589        session.updateLocked(autofillId, virtualBounds, value, action, flags);
590        return false;
591    }
592
593    @GuardedBy("mLock")
594    void removeSessionLocked(int sessionId) {
595        mSessions.remove(sessionId);
596    }
597
598    void handleSessionSave(Session session) {
599        synchronized (mLock) {
600            if (mSessions.get(session.id) == null) {
601                Slog.w(TAG, "handleSessionSave(): already gone: " + session.id);
602
603                return;
604            }
605            session.callSaveLocked();
606        }
607    }
608
609    void onPendingSaveUi(int operation, @NonNull IBinder token) {
610        if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
611        synchronized (mLock) {
612            final int sessionCount = mSessions.size();
613            for (int i = sessionCount - 1; i >= 0; i--) {
614                final Session session = mSessions.valueAt(i);
615                if (session.isSaveUiPendingForTokenLocked(token)) {
616                    session.onPendingSaveUi(operation, token);
617                    return;
618                }
619            }
620        }
621        if (sDebug) {
622            Slog.d(TAG, "No pending Save UI for token " + token + " and operation "
623                    + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_",
624                            operation));
625        }
626    }
627
628    @GuardedBy("mLock")
629    void handlePackageUpdateLocked(String packageName) {
630        final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo();
631        if (serviceInfo != null && serviceInfo.packageName.equals(packageName)) {
632            resetExtServiceLocked();
633        }
634    }
635
636    @GuardedBy("mLock")
637    void resetExtServiceLocked() {
638        if (sVerbose) Slog.v(TAG, "reset autofill service.");
639        mFieldClassificationStrategy.reset();
640    }
641
642    @GuardedBy("mLock")
643    void destroyLocked() {
644        if (sVerbose) Slog.v(TAG, "destroyLocked()");
645
646        resetExtServiceLocked();
647
648        final int numSessions = mSessions.size();
649        final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
650        for (int i = 0; i < numSessions; i++) {
651            final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
652            if (remoteFillService != null) {
653                remoteFillServices.add(remoteFillService);
654            }
655        }
656        mSessions.clear();
657        for (int i = 0; i < remoteFillServices.size(); i++) {
658            remoteFillServices.valueAt(i).destroy();
659        }
660
661        sendStateToClients(true);
662        if (mClients != null) {
663            mClients.kill();
664            mClients = null;
665        }
666    }
667
668    @NonNull
669    CharSequence getServiceLabel() {
670        return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
671    }
672
673    @NonNull
674    Drawable getServiceIcon() {
675        return mInfo.getServiceInfo().loadIcon(mContext.getPackageManager());
676    }
677
678    /**
679     * Initializes the last fill selection after an autofill service returned a new
680     * {@link FillResponse}.
681     */
682    void setLastResponse(int sessionId, @NonNull FillResponse response) {
683        synchronized (mLock) {
684            mEventHistory = new FillEventHistory(sessionId, response.getClientState());
685        }
686    }
687
688    /**
689     * Resets the last fill selection.
690     */
691    void resetLastResponse() {
692        synchronized (mLock) {
693            mEventHistory = null;
694        }
695    }
696
697    @GuardedBy("mLock")
698    private boolean isValidEventLocked(String method, int sessionId) {
699        if (mEventHistory == null) {
700            Slog.w(TAG, method + ": not logging event because history is null");
701            return false;
702        }
703        if (sessionId != mEventHistory.getSessionId()) {
704            if (sDebug) {
705                Slog.d(TAG, method + ": not logging event for session " + sessionId
706                        + " because tracked session is " + mEventHistory.getSessionId());
707            }
708            return false;
709        }
710        return true;
711    }
712
713    /**
714     * Updates the last fill selection when an authentication was selected.
715     */
716    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
717        synchronized (mLock) {
718            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
719                mEventHistory.addEvent(
720                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
721                                null, null, null, null, null, null));
722            }
723        }
724    }
725
726    /**
727     * Updates the last fill selection when an dataset authentication was selected.
728     */
729    void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
730            @Nullable Bundle clientState) {
731        synchronized (mLock) {
732            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
733                mEventHistory.addEvent(
734                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
735                                clientState, null, null, null, null, null, null, null, null));
736            }
737        }
738    }
739
740    /**
741     * Updates the last fill selection when an save Ui is shown.
742     */
743    void logSaveShown(int sessionId, @Nullable Bundle clientState) {
744        synchronized (mLock) {
745            if (isValidEventLocked("logSaveShown()", sessionId)) {
746                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
747                        null, null, null, null, null, null, null));
748            }
749        }
750    }
751
752    /**
753     * Updates the last fill response when a dataset was selected.
754     */
755    void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
756            @Nullable Bundle clientState) {
757        synchronized (mLock) {
758            if (isValidEventLocked("logDatasetSelected()", sessionId)) {
759                mEventHistory.addEvent(
760                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
761                                null, null, null, null, null, null, null));
762            }
763        }
764    }
765
766    /**
767     * Updates the last fill response when an autofill context is committed.
768     */
769    @GuardedBy("mLock")
770    void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
771            @Nullable ArrayList<String> selectedDatasets,
772            @Nullable ArraySet<String> ignoredDatasets,
773            @Nullable ArrayList<AutofillId> changedFieldIds,
774            @Nullable ArrayList<String> changedDatasetIds,
775            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
776            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
777            @NonNull String appPackageName, boolean compatMode) {
778        logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
779                changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
780                manuallyFilledDatasetIds, null, null, appPackageName, compatMode);
781    }
782
783    @GuardedBy("mLock")
784    void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
785            @Nullable ArrayList<String> selectedDatasets,
786            @Nullable ArraySet<String> ignoredDatasets,
787            @Nullable ArrayList<AutofillId> changedFieldIds,
788            @Nullable ArrayList<String> changedDatasetIds,
789            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
790            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
791            @Nullable ArrayList<AutofillId> detectedFieldIdsList,
792            @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
793            @NonNull String appPackageName, boolean compatMode) {
794        if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
795            if (sVerbose) {
796                Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
797                        + ", selectedDatasets=" + selectedDatasets
798                        + ", ignoredDatasetIds=" + ignoredDatasets
799                        + ", changedAutofillIds=" + changedFieldIds
800                        + ", changedDatasetIds=" + changedDatasetIds
801                        + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds
802                        + ", detectedFieldIds=" + detectedFieldIdsList
803                        + ", detectedFieldClassifications=" + detectedFieldClassificationsList
804                        + ", compatMode=" + compatMode);
805            }
806            AutofillId[] detectedFieldsIds = null;
807            FieldClassification[] detectedFieldClassifications = null;
808            if (detectedFieldIdsList != null) {
809                detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
810                detectedFieldIdsList.toArray(detectedFieldsIds);
811                detectedFieldClassifications =
812                        new FieldClassification[detectedFieldClassificationsList.size()];
813                detectedFieldClassificationsList.toArray(detectedFieldClassifications);
814
815                final int numberFields = detectedFieldsIds.length;
816                int totalSize = 0;
817                float totalScore = 0;
818                for (int i = 0; i < numberFields; i++) {
819                    final FieldClassification fc = detectedFieldClassifications[i];
820                    final List<Match> matches = fc.getMatches();
821                    final int size = matches.size();
822                    totalSize += size;
823                    for (int j = 0; j < size; j++) {
824                        totalScore += matches.get(j).getScore();
825                    }
826                }
827
828                final int averageScore = (int) ((totalScore * 100) / totalSize);
829                mMetricsLogger.write(Helper
830                        .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
831                                appPackageName, getServicePackageName(), compatMode)
832                        .setCounterValue(numberFields)
833                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
834                                averageScore));
835            }
836            mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
837                    clientState, selectedDatasets, ignoredDatasets,
838                    changedFieldIds, changedDatasetIds,
839                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
840                    detectedFieldsIds, detectedFieldClassifications));
841        }
842    }
843
844    /**
845     * Gets the fill event history.
846     *
847     * @param callingUid The calling uid
848     *
849     * @return The history or {@code null} if there is none.
850     */
851    FillEventHistory getFillEventHistory(int callingUid) {
852        synchronized (mLock) {
853            if (mEventHistory != null
854                    && isCalledByServiceLocked("getFillEventHistory", callingUid)) {
855                return mEventHistory;
856            }
857        }
858        return null;
859    }
860
861    // Called by Session - does not need to check uid
862    UserData getUserData() {
863        synchronized (mLock) {
864            return mUserData;
865        }
866    }
867
868    // Called by AutofillManager
869    UserData getUserData(int callingUid) {
870        synchronized (mLock) {
871            if (isCalledByServiceLocked("getUserData", callingUid)) {
872                return mUserData;
873            }
874        }
875        return null;
876    }
877
878    // Called by AutofillManager
879    void setUserData(int callingUid, UserData userData) {
880        synchronized (mLock) {
881            if (!isCalledByServiceLocked("setUserData", callingUid)) {
882                return;
883            }
884            mUserData = userData;
885            // Log it
886            int numberFields = mUserData == null ? 0: mUserData.getCategoryIds().length;
887            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED,
888                    getServicePackageName(), null)
889                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, numberFields));
890        }
891    }
892
893    @GuardedBy("mLock")
894    private boolean isCalledByServiceLocked(String methodName, int callingUid) {
895        if (getServiceUidLocked() != callingUid) {
896            Slog.w(TAG, methodName + "() called by UID " + callingUid
897                    + ", but service UID is " + getServiceUidLocked());
898            return false;
899        }
900        return true;
901    }
902
903    @GuardedBy("mLock")
904    void dumpLocked(String prefix, PrintWriter pw) {
905        final String prefix2 = prefix + "  ";
906
907        pw.print(prefix); pw.print("User: "); pw.println(mUserId);
908        pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked());
909        pw.print(prefix); pw.print("Autofill Service Info: ");
910        if (mInfo == null) {
911            pw.println("N/A");
912        } else {
913            pw.println();
914            mInfo.dump(prefix2, pw);
915        }
916        pw.print(prefix); pw.print("Component from settings: ");
917            pw.println(getComponentNameFromSettings());
918        pw.print(prefix); pw.print("Default component: ");
919            pw.println(mContext.getString(R.string.config_defaultAutofillService));
920        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
921        pw.print(prefix); pw.print("Field classification enabled: ");
922            pw.println(isFieldClassificationEnabledLocked());
923        pw.print(prefix); pw.print("Compat pkgs: ");
924        final ArrayMap<String, Long> compatPkgs = getCompatibilityPackagesLocked();
925        if (compatPkgs == null) {
926            pw.println("N/A");
927        } else {
928            pw.println(compatPkgs);
929        }
930        pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
931        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
932
933        pw.print(prefix); pw.print("Disabled apps: ");
934
935        if (mDisabledApps == null) {
936            pw.println("N/A");
937        } else {
938            final int size = mDisabledApps.size();
939            pw.println(size);
940            final StringBuilder builder = new StringBuilder();
941            final long now = SystemClock.elapsedRealtime();
942            for (int i = 0; i < size; i++) {
943                final String packageName = mDisabledApps.keyAt(i);
944                final long expiration = mDisabledApps.valueAt(i);
945                 builder.append(prefix).append(prefix)
946                     .append(i).append(". ").append(packageName).append(": ");
947                 TimeUtils.formatDuration((expiration - now), builder);
948                 builder.append('\n');
949             }
950             pw.println(builder);
951        }
952
953        pw.print(prefix); pw.print("Disabled activities: ");
954
955        if (mDisabledActivities == null) {
956            pw.println("N/A");
957        } else {
958            final int size = mDisabledActivities.size();
959            pw.println(size);
960            final StringBuilder builder = new StringBuilder();
961            final long now = SystemClock.elapsedRealtime();
962            for (int i = 0; i < size; i++) {
963                final ComponentName component = mDisabledActivities.keyAt(i);
964                final long expiration = mDisabledActivities.valueAt(i);
965                 builder.append(prefix).append(prefix)
966                     .append(i).append(". ").append(component).append(": ");
967                 TimeUtils.formatDuration((expiration - now), builder);
968                 builder.append('\n');
969             }
970             pw.println(builder);
971        }
972
973        final int size = mSessions.size();
974        if (size == 0) {
975            pw.print(prefix); pw.println("No sessions");
976        } else {
977            pw.print(prefix); pw.print(size); pw.println(" sessions:");
978            for (int i = 0; i < size; i++) {
979                pw.print(prefix); pw.print("#"); pw.println(i + 1);
980                mSessions.valueAt(i).dumpLocked(prefix2, pw);
981            }
982        }
983
984        pw.print(prefix); pw.print("Clients: ");
985        if (mClients == null) {
986            pw.println("N/A");
987        } else {
988            pw.println();
989            mClients.dump(pw, prefix2);
990        }
991
992        if (mEventHistory == null || mEventHistory.getEvents() == null
993                || mEventHistory.getEvents().size() == 0) {
994            pw.print(prefix); pw.println("No event on last fill response");
995        } else {
996            pw.print(prefix); pw.println("Events of last fill response:");
997            pw.print(prefix);
998
999            int numEvents = mEventHistory.getEvents().size();
1000            for (int i = 0; i < numEvents; i++) {
1001                final Event event = mEventHistory.getEvents().get(i);
1002                pw.println("  " + i + ": eventType=" + event.getType() + " datasetId="
1003                        + event.getDatasetId());
1004            }
1005        }
1006
1007        pw.print(prefix); pw.print("User data: ");
1008        if (mUserData == null) {
1009            pw.println("N/A");
1010        } else {
1011            pw.println();
1012            mUserData.dump(prefix2, pw);
1013        }
1014
1015        pw.print(prefix); pw.println("Field Classification strategy: ");
1016        mFieldClassificationStrategy.dump(prefix2, pw);
1017    }
1018
1019    @GuardedBy("mLock")
1020    void destroySessionsLocked() {
1021        if (mSessions.size() == 0) {
1022            mUi.destroyAll(null, null, false);
1023            return;
1024        }
1025        while (mSessions.size() > 0) {
1026            mSessions.valueAt(0).forceRemoveSelfLocked();
1027        }
1028    }
1029
1030    // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
1031    @GuardedBy("mLock")
1032    void destroyFinishedSessionsLocked() {
1033        final int sessionCount = mSessions.size();
1034        for (int i = sessionCount - 1; i >= 0; i--) {
1035            final Session session = mSessions.valueAt(i);
1036            if (session.isSavingLocked()) {
1037                if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
1038                session.forceRemoveSelfLocked();
1039            }
1040        }
1041    }
1042
1043    @GuardedBy("mLock")
1044    void listSessionsLocked(ArrayList<String> output) {
1045        final int numSessions = mSessions.size();
1046        for (int i = 0; i < numSessions; i++) {
1047            output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
1048                    : null) + ":" + mSessions.keyAt(i));
1049        }
1050    }
1051
1052    @GuardedBy("mLock")
1053    @Nullable ArrayMap<String, Long> getCompatibilityPackagesLocked() {
1054        if (mInfo != null) {
1055            return mInfo.getCompatibilityPackages();
1056        }
1057        return null;
1058    }
1059
1060    private void sendStateToClients(boolean resetClient) {
1061        final RemoteCallbackList<IAutoFillManagerClient> clients;
1062        final int userClientCount;
1063        synchronized (mLock) {
1064            if (mClients == null) {
1065                return;
1066            }
1067            clients = mClients;
1068            userClientCount = clients.beginBroadcast();
1069        }
1070        try {
1071            for (int i = 0; i < userClientCount; i++) {
1072                final IAutoFillManagerClient client = clients.getBroadcastItem(i);
1073                try {
1074                    final boolean resetSession;
1075                    final boolean isEnabled;
1076                    synchronized (mLock) {
1077                        resetSession = resetClient || isClientSessionDestroyedLocked(client);
1078                        isEnabled = isEnabledLocked();
1079                    }
1080                    int flags = 0;
1081                    if (isEnabled) {
1082                        flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
1083                    }
1084                    if (resetSession) {
1085                        flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION;
1086                    }
1087                    if (resetClient) {
1088                        flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT;
1089                    }
1090                    if (sDebug) {
1091                        flags |= AutofillManager.SET_STATE_FLAG_DEBUG;
1092                    }
1093                    if (sVerbose) {
1094                        flags |= AutofillManager.SET_STATE_FLAG_VERBOSE;
1095                    }
1096                    client.setState(flags);
1097                } catch (RemoteException re) {
1098                    /* ignore */
1099                }
1100            }
1101        } finally {
1102            clients.finishBroadcast();
1103        }
1104    }
1105
1106    @GuardedBy("mLock")
1107    private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
1108        final int sessionCount = mSessions.size();
1109        for (int i = 0; i < sessionCount; i++) {
1110            final Session session = mSessions.valueAt(i);
1111            if (session.getClient().equals(client)) {
1112                return session.isDestroyed();
1113            }
1114        }
1115        return true;
1116    }
1117
1118    @GuardedBy("mLock")
1119    boolean isEnabledLocked() {
1120        return mSetupComplete && mInfo != null && !mDisabled;
1121    }
1122
1123    /**
1124     * Called by {@link Session} when service asked to disable autofill for an app.
1125     */
1126    void disableAutofillForApp(@NonNull String packageName, long duration, boolean compatMode) {
1127        synchronized (mLock) {
1128            if (mDisabledApps == null) {
1129                mDisabledApps = new ArrayMap<>(1);
1130            }
1131            long expiration = SystemClock.elapsedRealtime() + duration;
1132            // Protect it against overflow
1133            if (expiration < 0) {
1134                expiration = Long.MAX_VALUE;
1135            }
1136            mDisabledApps.put(packageName, expiration);
1137            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
1138            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
1139                    packageName, getServicePackageName(), compatMode)
1140                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
1141        }
1142    }
1143
1144    /**
1145     * Called by {@link Session} when service asked to disable autofill an app.
1146     */
1147    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration,
1148            boolean compatMode) {
1149        synchronized (mLock) {
1150            if (mDisabledActivities == null) {
1151                mDisabledActivities = new ArrayMap<>(1);
1152            }
1153            long expiration = SystemClock.elapsedRealtime() + duration;
1154            // Protect it against overflow
1155            if (expiration < 0) {
1156                expiration = Long.MAX_VALUE;
1157            }
1158            mDisabledActivities.put(componentName, expiration);
1159            final int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
1160            // NOTE: not using Helper.newLogMaker() because we're setting the componentName instead
1161            // of package name
1162            mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY)
1163                    .setComponentName(componentName)
1164                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
1165                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)
1166                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0));
1167        }
1168    }
1169
1170    /**
1171     * Checks if autofill is disabled by service to the given activity.
1172     */
1173    @GuardedBy("mLock")
1174    private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
1175        // Check activities first.
1176        long elapsedTime = 0;
1177        if (mDisabledActivities != null) {
1178            elapsedTime = SystemClock.elapsedRealtime();
1179            final Long expiration = mDisabledActivities.get(componentName);
1180            if (expiration != null) {
1181                if (expiration >= elapsedTime) return true;
1182                // Restriction expired - clean it up.
1183                if (sVerbose) {
1184                    Slog.v(TAG, "Removing " + componentName.toShortString()
1185                        + " from disabled list");
1186                }
1187                mDisabledActivities.remove(componentName);
1188            }
1189        }
1190
1191        // Then check apps.
1192        final String packageName = componentName.getPackageName();
1193        if (mDisabledApps == null) return false;
1194
1195        final Long expiration = mDisabledApps.get(packageName);
1196        if (expiration == null) return false;
1197
1198        if (elapsedTime == 0) {
1199            elapsedTime = SystemClock.elapsedRealtime();
1200        }
1201
1202        if (expiration >= elapsedTime) return true;
1203
1204        // Restriction expired - clean it up.
1205        if (sVerbose)  Slog.v(TAG, "Removing " + packageName + " from disabled list");
1206        mDisabledApps.remove(packageName);
1207        return false;
1208    }
1209
1210    // Called by AutofillManager, checks UID.
1211    boolean isFieldClassificationEnabled(int callingUid) {
1212        synchronized (mLock) {
1213            if (!isCalledByServiceLocked("isFieldClassificationEnabled", callingUid)) {
1214                return false;
1215            }
1216            return isFieldClassificationEnabledLocked();
1217        }
1218    }
1219
1220    // Called by internally, no need to check UID.
1221    boolean isFieldClassificationEnabledLocked() {
1222        return Settings.Secure.getIntForUser(
1223                mContext.getContentResolver(),
1224                Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 1,
1225                mUserId) == 1;
1226    }
1227
1228    FieldClassificationStrategy getFieldClassificationStrategy() {
1229        return mFieldClassificationStrategy;
1230    }
1231
1232    String[] getAvailableFieldClassificationAlgorithms(int callingUid) {
1233        synchronized (mLock) {
1234            if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) {
1235                return null;
1236            }
1237        }
1238        return mFieldClassificationStrategy.getAvailableAlgorithms();
1239    }
1240
1241    String getDefaultFieldClassificationAlgorithm(int callingUid) {
1242        synchronized (mLock) {
1243            if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) {
1244                return null;
1245            }
1246        }
1247        return mFieldClassificationStrategy.getDefaultAlgorithm();
1248    }
1249
1250    @Override
1251    public String toString() {
1252        return "AutofillManagerServiceImpl: [userId=" + mUserId
1253                + ", component=" + (mInfo != null
1254                ? mInfo.getServiceInfo().getComponentName() : null) + "]";
1255    }
1256
1257    /** Task used to prune abandoned session */
1258    private class PruneTask extends AsyncTask<Void, Void, Void> {
1259        @Override
1260        protected Void doInBackground(Void... ignored) {
1261            int numSessionsToRemove;
1262
1263            SparseArray<IBinder> sessionsToRemove;
1264
1265            synchronized (mLock) {
1266                numSessionsToRemove = mSessions.size();
1267                sessionsToRemove = new SparseArray<>(numSessionsToRemove);
1268
1269                for (int i = 0; i < numSessionsToRemove; i++) {
1270                    Session session = mSessions.valueAt(i);
1271
1272                    sessionsToRemove.put(session.id, session.getActivityTokenLocked());
1273                }
1274            }
1275
1276            IActivityManager am = ActivityManager.getService();
1277
1278            // Only remove sessions which's activities are not known to the activity manager anymore
1279            for (int i = 0; i < numSessionsToRemove; i++) {
1280                try {
1281                    // The activity manager cannot resolve activities that have been removed
1282                    if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
1283                        sessionsToRemove.removeAt(i);
1284                        i--;
1285                        numSessionsToRemove--;
1286                    }
1287                } catch (RemoteException e) {
1288                    Slog.w(TAG, "Cannot figure out if activity is finished", e);
1289                }
1290            }
1291
1292            synchronized (mLock) {
1293                for (int i = 0; i < numSessionsToRemove; i++) {
1294                    Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
1295
1296                    if (sessionToRemove != null && sessionsToRemove.valueAt(i)
1297                            == sessionToRemove.getActivityTokenLocked()) {
1298                        if (sessionToRemove.isSavingLocked()) {
1299                            if (sVerbose) {
1300                                Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
1301                            }
1302                        } else {
1303                            if (sDebug) {
1304                                Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
1305                                    + sessionToRemove.getActivityTokenLocked() + ")");
1306                            }
1307                            sessionToRemove.removeSelfLocked();
1308                        }
1309                    }
1310                }
1311            }
1312
1313            return null;
1314        }
1315    }
1316}
1317