AutofillManagerServiceImpl.java revision 2e30c6f371be6211f1fa2b2257084df24a535795
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.AppGlobals;
30import android.app.IActivityManager;
31import android.content.ComponentName;
32import android.content.Context;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.ServiceInfo;
36import android.graphics.Rect;
37import android.os.AsyncTask;
38import android.os.Binder;
39import android.os.Bundle;
40import android.os.IBinder;
41import android.os.Looper;
42import android.os.RemoteCallbackList;
43import android.os.RemoteException;
44import android.os.UserHandle;
45import android.os.UserManager;
46import android.provider.Settings;
47import android.service.autofill.AutofillService;
48import android.service.autofill.AutofillServiceInfo;
49import android.service.autofill.FillEventHistory;
50import android.service.autofill.FillEventHistory.Event;
51import android.service.autofill.FillResponse;
52import android.service.autofill.IAutoFillService;
53import android.text.TextUtils;
54import android.util.ArraySet;
55import android.util.LocalLog;
56import android.util.Slog;
57import android.util.SparseArray;
58import android.view.autofill.AutofillId;
59import android.view.autofill.AutofillValue;
60import android.view.autofill.IAutoFillManagerClient;
61
62import com.android.internal.R;
63import com.android.internal.annotations.GuardedBy;
64import com.android.internal.os.HandlerCaller;
65import com.android.server.autofill.ui.AutoFillUI;
66
67import java.io.PrintWriter;
68import java.util.ArrayList;
69import java.util.Random;
70
71/**
72 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
73 * app's {@link IAutoFillService} implementation.
74 *
75 */
76final class AutofillManagerServiceImpl {
77
78    private static final String TAG = "AutofillManagerServiceImpl";
79    private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
80
81    /** Minimum interval to prune abandoned sessions */
82    private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
83
84    static final int MSG_SERVICE_SAVE = 1;
85
86    private final int mUserId;
87    private final Context mContext;
88    private final Object mLock;
89    private final AutoFillUI mUi;
90
91    private RemoteCallbackList<IAutoFillManagerClient> mClients;
92    private AutofillServiceInfo mInfo;
93
94    private static final Random sRandom = new Random();
95
96    private final LocalLog mRequestsHistory;
97    /**
98     * Whether service was disabled for user due to {@link UserManager} restrictions.
99     */
100    private boolean mDisabled;
101
102    /**
103     * Caches whether the setup completed for the current user.
104     */
105    @GuardedBy("mLock")
106    private boolean mSetupComplete;
107
108    private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
109        switch (msg.what) {
110            case MSG_SERVICE_SAVE:
111                handleSessionSave(msg.arg1);
112                break;
113            default:
114                Slog.w(TAG, "invalid msg on handler: " + msg);
115        }
116    };
117
118    private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
119            mHandlerCallback, true);
120
121    /**
122     * Cache of pending {@link Session}s, keyed by sessionId.
123     *
124     * <p>They're kept until the {@link AutofillService} finished handling a request, an error
125     * occurs, or the session is abandoned.
126     */
127    @GuardedBy("mLock")
128    private final SparseArray<Session> mSessions = new SparseArray<>();
129
130    /** The last selection */
131    @GuardedBy("mLock")
132    private FillEventHistory mEventHistory;
133
134    /** When was {@link PruneTask} last executed? */
135    private long mLastPrune = 0;
136
137    AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
138            int userId, AutoFillUI ui, boolean disabled) {
139        mContext = context;
140        mLock = lock;
141        mRequestsHistory = requestsHistory;
142        mUserId = userId;
143        mUi = ui;
144        updateLocked(disabled);
145    }
146
147    CharSequence getServiceName() {
148        final String packageName = getPackageName();
149        if (packageName == null) {
150            return null;
151        }
152
153        try {
154            final PackageManager pm = mContext.getPackageManager();
155            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
156            return pm.getApplicationLabel(info);
157        } catch (Exception e) {
158            Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
159            return packageName;
160        }
161    }
162
163    String getPackageName() {
164        final ComponentName serviceComponent = getServiceComponentName();
165        if (serviceComponent != null) {
166            return serviceComponent.getPackageName();
167        }
168        return null;
169    }
170
171    ComponentName getServiceComponentName() {
172        synchronized (mLock) {
173            if (mInfo == null) {
174                return null;
175            }
176            return mInfo.getServiceInfo().getComponentName();
177        }
178    }
179
180    private boolean isSetupCompletedLocked() {
181        final String setupComplete = Settings.Secure.getStringForUser(
182                mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
183        return "1".equals(setupComplete);
184    }
185
186    private String getComponentNameFromSettings() {
187        return Settings.Secure.getStringForUser(
188                mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
189    }
190
191    void updateLocked(boolean disabled) {
192        final boolean wasEnabled = isEnabled();
193        if (sVerbose) {
194            Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
195                    + ", mSetupComplete= " + mSetupComplete
196                    + ", disabled=" + disabled + ", mDisabled=" + mDisabled);
197        }
198        mSetupComplete = isSetupCompletedLocked();
199        mDisabled = disabled;
200        ComponentName serviceComponent = null;
201        ServiceInfo serviceInfo = null;
202        final String componentName = getComponentNameFromSettings();
203        if (!TextUtils.isEmpty(componentName)) {
204            try {
205                serviceComponent = ComponentName.unflattenFromString(componentName);
206                serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
207                        0, mUserId);
208            } catch (RuntimeException | RemoteException e) {
209                Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
210                return;
211            }
212        }
213        try {
214            if (serviceInfo != null) {
215                mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
216                        serviceComponent, mUserId);
217            } else {
218                mInfo = null;
219            }
220            final boolean isEnabled = isEnabled();
221            if (wasEnabled != isEnabled) {
222                if (!isEnabled) {
223                    final int sessionCount = mSessions.size();
224                    for (int i = sessionCount - 1; i >= 0; i--) {
225                        final Session session = mSessions.valueAt(i);
226                        session.removeSelfLocked();
227                    }
228                }
229                sendStateToClients(false);
230            }
231        } catch (Exception e) {
232            Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
233        }
234    }
235
236    /**
237     * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app.
238     */
239    void requestSaveForUserLocked(IBinder activityToken) {
240        if (!isEnabled()) {
241            return;
242        }
243
244        final int numSessions = mSessions.size();
245        for (int i = 0; i < numSessions; i++) {
246            final Session session = mSessions.valueAt(i);
247            if (session.getActivityTokenLocked().equals(activityToken)) {
248                session.callSaveLocked();
249                return;
250            }
251        }
252
253        Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
254    }
255
256    boolean addClientLocked(IAutoFillManagerClient client) {
257        if (mClients == null) {
258            mClients = new RemoteCallbackList<>();
259        }
260        mClients.register(client);
261        return isEnabled();
262    }
263
264    void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
265        if (!isEnabled()) {
266            return;
267        }
268        final Session session = mSessions.get(sessionId);
269        if (session != null && uid == session.uid) {
270            session.setAuthenticationResultLocked(data, authenticationId);
271        }
272    }
273
274    void setHasCallback(int sessionId, int uid, boolean hasIt) {
275        if (!isEnabled()) {
276            return;
277        }
278        final Session session = mSessions.get(sessionId);
279        if (session != null && uid == session.uid) {
280            synchronized (mLock) {
281                session.setHasCallbackLocked(hasIt);
282            }
283        }
284    }
285
286    int startSessionLocked(@NonNull IBinder activityToken, int uid,
287            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
288            @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
289            int flags, @NonNull String packageName) {
290        if (!isEnabled()) {
291            return 0;
292        }
293
294        // Occasionally clean up abandoned sessions
295        pruneAbandonedSessionsLocked();
296
297        final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
298                hasCallback, packageName);
299        if (newSession == null) {
300            return NO_SESSION;
301        }
302
303        final String historyItem =
304                "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
305                        + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
306                        hasCallback + " f=" + flags;
307        mRequestsHistory.log(historyItem);
308
309        newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
310
311        return newSession.id;
312    }
313
314    /**
315     * Remove abandoned sessions if needed.
316     */
317    private void pruneAbandonedSessionsLocked() {
318        long now = System.currentTimeMillis();
319        if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
320            mLastPrune = now;
321
322            if (mSessions.size() > 0) {
323                (new PruneTask()).execute();
324            }
325        }
326    }
327
328    void finishSessionLocked(int sessionId, int uid) {
329        if (!isEnabled()) {
330            return;
331        }
332
333        final Session session = mSessions.get(sessionId);
334        if (session == null || uid != session.uid) {
335            if (sVerbose) {
336                Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")");
337            }
338            return;
339        }
340
341        final boolean finished = session.showSaveLocked();
342        if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
343
344        if (finished) {
345            session.removeSelfLocked();
346        }
347    }
348
349    void cancelSessionLocked(int sessionId, int uid) {
350        if (!isEnabled()) {
351            return;
352        }
353
354        final Session session = mSessions.get(sessionId);
355        if (session == null || uid != session.uid) {
356            Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
357            return;
358        }
359        session.removeSelfLocked();
360    }
361
362    void disableOwnedAutofillServicesLocked(int uid) {
363        if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid
364                != UserHandle.getAppId(uid)) {
365            return;
366        }
367        final long identity = Binder.clearCallingIdentity();
368        try {
369            final String autoFillService = getComponentNameFromSettings();
370            if (mInfo.getServiceInfo().getComponentName().equals(
371                    ComponentName.unflattenFromString(autoFillService))) {
372                Settings.Secure.putStringForUser(mContext.getContentResolver(),
373                        Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
374                destroySessionsLocked();
375            }
376        } finally {
377            Binder.restoreCallingIdentity(identity);
378        }
379    }
380
381    private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
382            @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
383        // use random ids so that one app cannot know that another app creates sessions
384        int sessionId;
385        int tries = 0;
386        do {
387            tries++;
388            if (tries > MAX_SESSION_ID_CREATE_TRIES) {
389                Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
390                return null;
391            }
392
393            sessionId = sRandom.nextInt();
394        } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
395
396        final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
397                sessionId, uid, activityToken, appCallbackToken, hasCallback,
398                mInfo.getServiceInfo().getComponentName(), packageName);
399        mSessions.put(newSession.id, newSession);
400
401        return newSession;
402    }
403
404    /**
405     * Restores a session after an activity was temporarily destroyed.
406     *
407     * @param sessionId The id of the session to restore
408     * @param uid UID of the process that tries to restore the session
409     * @param activityToken The new instance of the activity
410     * @param appCallback The callbacks to the activity
411     */
412    boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
413            @NonNull IBinder appCallback) {
414        final Session session = mSessions.get(sessionId);
415
416        if (session == null || uid != session.uid) {
417            return false;
418        } else {
419            session.switchActivity(activityToken, appCallback);
420            return true;
421        }
422    }
423
424    /**
425     * Updates a session and returns whether it should be restarted.
426     */
427    boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
428            AutofillValue value, int action, int flags) {
429        final Session session = mSessions.get(sessionId);
430        if (session == null || session.uid != uid) {
431            if ((flags & FLAG_MANUAL_REQUEST) != 0) {
432                if (sDebug) {
433                    Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
434                            + autofillId);
435                }
436                return true;
437            }
438            if (sVerbose) {
439                Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
440                        + "(" + uid + ")");
441            }
442            return false;
443        }
444
445        session.updateLocked(autofillId, virtualBounds, value, action, flags);
446        return false;
447    }
448
449    void removeSessionLocked(int sessionId) {
450        mSessions.remove(sessionId);
451    }
452
453    private void handleSessionSave(int sessionId) {
454        synchronized (mLock) {
455            final Session session = mSessions.get(sessionId);
456            if (session == null) {
457                Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
458
459                return;
460            }
461            session.callSaveLocked();
462        }
463    }
464
465    void destroyLocked() {
466        if (sVerbose) Slog.v(TAG, "destroyLocked()");
467
468        final int numSessions = mSessions.size();
469        final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
470        for (int i = 0; i < numSessions; i++) {
471            final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
472            if (remoteFillService != null) {
473                remoteFillServices.add(remoteFillService);
474            }
475        }
476        mSessions.clear();
477        for (int i = 0; i < remoteFillServices.size(); i++) {
478            remoteFillServices.valueAt(i).destroy();
479        }
480
481        sendStateToClients(true);
482    }
483
484    CharSequence getServiceLabel() {
485        return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
486    }
487
488    /**
489     * Initializes the last fill selection after an autofill service returned a new
490     * {@link FillResponse}.
491     */
492    void setLastResponse(int serviceUid, @NonNull FillResponse response) {
493        synchronized (mLock) {
494            mEventHistory = new FillEventHistory(serviceUid, response.getClientState());
495        }
496    }
497
498    /**
499     * Resets the last fill selection.
500     */
501    void resetLastResponse() {
502        synchronized (mLock) {
503            mEventHistory = null;
504        }
505    }
506
507    /**
508     * Updates the last fill selection when an authentication was selected.
509     */
510    void setAuthenticationSelected() {
511        synchronized (mLock) {
512            if (mEventHistory == null) {
513                Slog.w(TAG, "setAuthenticationSelected(): ignored when history is null");
514                return;
515            }
516            mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
517        }
518    }
519
520    /**
521     * Updates the last fill selection when an dataset authentication was selected.
522     */
523    void setDatasetAuthenticationSelected(@Nullable String selectedDataset) {
524        synchronized (mLock) {
525            if (mEventHistory == null) {
526                Slog.w(TAG, "setDatasetAuthenticationSelected(): ignored when history is null");
527                return;
528            }
529            mEventHistory.addEvent(
530                    new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
531        }
532    }
533
534    /**
535     * Updates the last fill selection when an save Ui is shown.
536     */
537    void setSaveShown() {
538        synchronized (mLock) {
539            if (mEventHistory == null) {
540                Slog.w(TAG, "setSaveShown(): ignored when history is null");
541                return;
542            }
543            mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
544        }
545    }
546
547    /**
548     * Updates the last fill response when a dataset was selected.
549     */
550    void setDatasetSelected(@Nullable String selectedDataset) {
551        synchronized (mLock) {
552            if (mEventHistory == null) {
553                Slog.w(TAG, "setDatasetSelected(): ignored when history is null");
554                return;
555            }
556            mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
557        }
558    }
559
560    /**
561     * Gets the fill event history.
562     *
563     * @param callingUid The calling uid
564     *
565     * @return The history or {@code null} if there is none.
566     */
567    FillEventHistory getFillEventHistory(int callingUid) {
568        synchronized (mLock) {
569            if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
570                return mEventHistory;
571            }
572        }
573
574        return null;
575    }
576
577    void dumpLocked(String prefix, PrintWriter pw) {
578        final String prefix2 = prefix + "  ";
579
580        pw.print(prefix); pw.print("User: "); pw.println(mUserId);
581        pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
582                ? mInfo.getServiceInfo().getComponentName() : null);
583        pw.print(prefix); pw.print("Component from settings: ");
584            pw.println(getComponentNameFromSettings());
585        pw.print(prefix); pw.print("Default component: ");
586            pw.println(mContext.getString(R.string.config_defaultAutofillService));
587        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
588        pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
589        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
590
591        final int size = mSessions.size();
592        if (size == 0) {
593            pw.print(prefix); pw.println("No sessions");
594        } else {
595            pw.print(prefix); pw.print(size); pw.println(" sessions:");
596            for (int i = 0; i < size; i++) {
597                pw.print(prefix); pw.print("#"); pw.println(i + 1);
598                mSessions.valueAt(i).dumpLocked(prefix2, pw);
599            }
600        }
601
602        if (mEventHistory == null || mEventHistory.getEvents() == null
603                || mEventHistory.getEvents().size() == 0) {
604            pw.print(prefix); pw.println("No event on last fill response");
605        } else {
606            pw.print(prefix); pw.println("Events of last fill response:");
607            pw.print(prefix);
608
609            int numEvents = mEventHistory.getEvents().size();
610            for (int i = 0; i < numEvents; i++) {
611                final Event event = mEventHistory.getEvents().get(i);
612                pw.println("  " + i + ": eventType=" + event.getType() + " datasetId="
613                        + event.getDatasetId());
614            }
615        }
616    }
617
618    void destroySessionsLocked() {
619        while (mSessions.size() > 0) {
620            mSessions.valueAt(0).removeSelfLocked();
621        }
622    }
623
624    void listSessionsLocked(ArrayList<String> output) {
625        final int numSessions = mSessions.size();
626        for (int i = 0; i < numSessions; i++) {
627            output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
628                    : null) + ":" + mSessions.keyAt(i));
629        }
630    }
631
632    private void sendStateToClients(boolean resetClient) {
633        final RemoteCallbackList<IAutoFillManagerClient> clients;
634        final int userClientCount;
635        synchronized (mLock) {
636            if (mClients == null) {
637                return;
638            }
639            clients = mClients;
640            userClientCount = clients.beginBroadcast();
641        }
642        try {
643            for (int i = 0; i < userClientCount; i++) {
644                final IAutoFillManagerClient client = clients.getBroadcastItem(i);
645                try {
646                    final boolean resetSession;
647                    synchronized (mLock) {
648                        resetSession = resetClient || isClientSessionDestroyedLocked(client);
649                    }
650                    client.setState(isEnabled(), resetSession, resetClient);
651                } catch (RemoteException re) {
652                    /* ignore */
653                }
654            }
655        } finally {
656            clients.finishBroadcast();
657        }
658    }
659
660    private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
661        final int sessionCount = mSessions.size();
662        for (int i = 0; i < sessionCount; i++) {
663            final Session session = mSessions.valueAt(i);
664            if (session.getClient().equals(client)) {
665                return session.isDestroyed();
666            }
667        }
668        return true;
669    }
670
671    boolean isEnabled() {
672        return mSetupComplete && mInfo != null && !mDisabled;
673    }
674
675    @Override
676    public String toString() {
677        return "AutofillManagerServiceImpl: [userId=" + mUserId
678                + ", component=" + (mInfo != null
679                ? mInfo.getServiceInfo().getComponentName() : null) + "]";
680    }
681
682    /** Task used to prune abandoned session */
683    private class PruneTask extends AsyncTask<Void, Void, Void> {
684        @Override
685        protected Void doInBackground(Void... ignored) {
686            int numSessionsToRemove;
687
688            SparseArray<IBinder> sessionsToRemove;
689
690            synchronized (mLock) {
691                numSessionsToRemove = mSessions.size();
692                sessionsToRemove = new SparseArray<>(numSessionsToRemove);
693
694                for (int i = 0; i < numSessionsToRemove; i++) {
695                    Session session = mSessions.valueAt(i);
696
697                    sessionsToRemove.put(session.id, session.getActivityTokenLocked());
698                }
699            }
700
701            IActivityManager am = ActivityManager.getService();
702
703            // Only remove sessions which's activities are not known to the activity manager anymore
704            for (int i = 0; i < numSessionsToRemove; i++) {
705                try {
706                    // The activity manager cannot resolve activities that have been removed
707                    if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
708                        sessionsToRemove.removeAt(i);
709                        i--;
710                        numSessionsToRemove--;
711                    }
712                } catch (RemoteException e) {
713                    Slog.w(TAG, "Cannot figure out if activity is finished", e);
714                }
715            }
716
717            synchronized (mLock) {
718                for (int i = 0; i < numSessionsToRemove; i++) {
719                    Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
720
721                    if (sessionToRemove != null && sessionsToRemove.valueAt(i)
722                            == sessionToRemove.getActivityTokenLocked()) {
723                        if (sessionToRemove.isSavingLocked()) {
724                            if (sVerbose) {
725                                Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
726                            }
727                        } else {
728                            if (sDebug) {
729                                Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
730                                    + sessionToRemove.getActivityTokenLocked() + ")");
731                            }
732                            sessionToRemove.removeSelfLocked();
733                        }
734                    }
735                }
736            }
737
738            return null;
739        }
740    }
741}
742