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 != uid) {
364            return;
365        }
366        final long identity = Binder.clearCallingIdentity();
367        try {
368            final String autoFillService = getComponentNameFromSettings();
369            if (mInfo.getServiceInfo().getComponentName().equals(
370                    ComponentName.unflattenFromString(autoFillService))) {
371                Settings.Secure.putStringForUser(mContext.getContentResolver(),
372                        Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
373                destroySessionsLocked();
374            }
375        } finally {
376            Binder.restoreCallingIdentity(identity);
377        }
378    }
379
380    private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
381            @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
382        // use random ids so that one app cannot know that another app creates sessions
383        int sessionId;
384        int tries = 0;
385        do {
386            tries++;
387            if (tries > MAX_SESSION_ID_CREATE_TRIES) {
388                Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
389                return null;
390            }
391
392            sessionId = sRandom.nextInt();
393        } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
394
395        final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
396                sessionId, uid, activityToken, appCallbackToken, hasCallback,
397                mInfo.getServiceInfo().getComponentName(), packageName);
398        mSessions.put(newSession.id, newSession);
399
400        return newSession;
401    }
402
403    /**
404     * Restores a session after an activity was temporarily destroyed.
405     *
406     * @param sessionId The id of the session to restore
407     * @param uid UID of the process that tries to restore the session
408     * @param activityToken The new instance of the activity
409     * @param appCallback The callbacks to the activity
410     */
411    boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
412            @NonNull IBinder appCallback) {
413        final Session session = mSessions.get(sessionId);
414
415        if (session == null || uid != session.uid) {
416            return false;
417        } else {
418            session.switchActivity(activityToken, appCallback);
419            return true;
420        }
421    }
422
423    /**
424     * Updates a session and returns whether it should be restarted.
425     */
426    boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
427            AutofillValue value, int action, int flags) {
428        final Session session = mSessions.get(sessionId);
429        if (session == null || session.uid != uid) {
430            if ((flags & FLAG_MANUAL_REQUEST) != 0) {
431                if (sDebug) {
432                    Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
433                            + autofillId);
434                }
435                return true;
436            }
437            if (sVerbose) {
438                Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
439                        + "(" + uid + ")");
440            }
441            return false;
442        }
443
444        session.updateLocked(autofillId, virtualBounds, value, action, flags);
445        return false;
446    }
447
448    void removeSessionLocked(int sessionId) {
449        mSessions.remove(sessionId);
450    }
451
452    private void handleSessionSave(int sessionId) {
453        synchronized (mLock) {
454            final Session session = mSessions.get(sessionId);
455            if (session == null) {
456                Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
457
458                return;
459            }
460            session.callSaveLocked();
461        }
462    }
463
464    void destroyLocked() {
465        if (sVerbose) Slog.v(TAG, "destroyLocked()");
466
467        final int numSessions = mSessions.size();
468        final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
469        for (int i = 0; i < numSessions; i++) {
470            final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
471            if (remoteFillService != null) {
472                remoteFillServices.add(remoteFillService);
473            }
474        }
475        mSessions.clear();
476        for (int i = 0; i < remoteFillServices.size(); i++) {
477            remoteFillServices.valueAt(i).destroy();
478        }
479
480        sendStateToClients(true);
481    }
482
483    CharSequence getServiceLabel() {
484        return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
485    }
486
487    /**
488     * Initializes the last fill selection after an autofill service returned a new
489     * {@link FillResponse}.
490     */
491    void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
492        synchronized (mLock) {
493            mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
494        }
495    }
496
497    /**
498     * Resets the last fill selection.
499     */
500    void resetLastResponse() {
501        synchronized (mLock) {
502            mEventHistory = null;
503        }
504    }
505
506    private boolean isValidEventLocked(String method, int sessionId) {
507        if (mEventHistory == null) {
508            Slog.w(TAG, method + ": not logging event because history is null");
509            return false;
510        }
511        if (sessionId != mEventHistory.getSessionId()) {
512            if (sDebug) {
513                Slog.d(TAG, method + ": not logging event for session " + sessionId
514                        + " because tracked session is " + mEventHistory.getSessionId());
515            }
516            return false;
517        }
518        return true;
519    }
520
521    /**
522     * Updates the last fill selection when an authentication was selected.
523     */
524    void setAuthenticationSelected(int sessionId) {
525        synchronized (mLock) {
526            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
527                mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
528            }
529        }
530    }
531
532    /**
533     * Updates the last fill selection when an dataset authentication was selected.
534     */
535    void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
536        synchronized (mLock) {
537            if (isValidEventLocked("setDatasetAuthenticationSelected()", sessionId)) {
538                mEventHistory.addEvent(
539                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
540            }
541        }
542    }
543
544    /**
545     * Updates the last fill selection when an save Ui is shown.
546     */
547    void setSaveShown(int sessionId) {
548        synchronized (mLock) {
549            if (isValidEventLocked("setSaveShown()", sessionId)) {
550                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
551            }
552        }
553    }
554
555    /**
556     * Updates the last fill response when a dataset was selected.
557     */
558    void setDatasetSelected(@Nullable String selectedDataset, int sessionId) {
559        synchronized (mLock) {
560            if (isValidEventLocked("setDatasetSelected()", sessionId)) {
561                mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
562            }
563        }
564    }
565
566    /**
567     * Gets the fill event history.
568     *
569     * @param callingUid The calling uid
570     *
571     * @return The history or {@code null} if there is none.
572     */
573    FillEventHistory getFillEventHistory(int callingUid) {
574        synchronized (mLock) {
575            if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
576                return mEventHistory;
577            }
578        }
579
580        return null;
581    }
582
583    void dumpLocked(String prefix, PrintWriter pw) {
584        final String prefix2 = prefix + "  ";
585
586        pw.print(prefix); pw.print("User: "); pw.println(mUserId);
587        pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
588                ? mInfo.getServiceInfo().getComponentName() : null);
589        pw.print(prefix); pw.print("Component from settings: ");
590            pw.println(getComponentNameFromSettings());
591        pw.print(prefix); pw.print("Default component: ");
592            pw.println(mContext.getString(R.string.config_defaultAutofillService));
593        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
594        pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
595        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
596
597        final int size = mSessions.size();
598        if (size == 0) {
599            pw.print(prefix); pw.println("No sessions");
600        } else {
601            pw.print(prefix); pw.print(size); pw.println(" sessions:");
602            for (int i = 0; i < size; i++) {
603                pw.print(prefix); pw.print("#"); pw.println(i + 1);
604                mSessions.valueAt(i).dumpLocked(prefix2, pw);
605            }
606        }
607
608        if (mEventHistory == null || mEventHistory.getEvents() == null
609                || mEventHistory.getEvents().size() == 0) {
610            pw.print(prefix); pw.println("No event on last fill response");
611        } else {
612            pw.print(prefix); pw.println("Events of last fill response:");
613            pw.print(prefix);
614
615            int numEvents = mEventHistory.getEvents().size();
616            for (int i = 0; i < numEvents; i++) {
617                final Event event = mEventHistory.getEvents().get(i);
618                pw.println("  " + i + ": eventType=" + event.getType() + " datasetId="
619                        + event.getDatasetId());
620            }
621        }
622    }
623
624    void destroySessionsLocked() {
625        while (mSessions.size() > 0) {
626            mSessions.valueAt(0).removeSelfLocked();
627        }
628    }
629
630    void listSessionsLocked(ArrayList<String> output) {
631        final int numSessions = mSessions.size();
632        for (int i = 0; i < numSessions; i++) {
633            output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
634                    : null) + ":" + mSessions.keyAt(i));
635        }
636    }
637
638    private void sendStateToClients(boolean resetClient) {
639        final RemoteCallbackList<IAutoFillManagerClient> clients;
640        final int userClientCount;
641        synchronized (mLock) {
642            if (mClients == null) {
643                return;
644            }
645            clients = mClients;
646            userClientCount = clients.beginBroadcast();
647        }
648        try {
649            for (int i = 0; i < userClientCount; i++) {
650                final IAutoFillManagerClient client = clients.getBroadcastItem(i);
651                try {
652                    final boolean resetSession;
653                    synchronized (mLock) {
654                        resetSession = resetClient || isClientSessionDestroyedLocked(client);
655                    }
656                    client.setState(isEnabled(), resetSession, resetClient);
657                } catch (RemoteException re) {
658                    /* ignore */
659                }
660            }
661        } finally {
662            clients.finishBroadcast();
663        }
664    }
665
666    private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
667        final int sessionCount = mSessions.size();
668        for (int i = 0; i < sessionCount; i++) {
669            final Session session = mSessions.valueAt(i);
670            if (session.getClient().equals(client)) {
671                return session.isDestroyed();
672            }
673        }
674        return true;
675    }
676
677    boolean isEnabled() {
678        return mSetupComplete && mInfo != null && !mDisabled;
679    }
680
681    @Override
682    public String toString() {
683        return "AutofillManagerServiceImpl: [userId=" + mUserId
684                + ", component=" + (mInfo != null
685                ? mInfo.getServiceInfo().getComponentName() : null) + "]";
686    }
687
688    /** Task used to prune abandoned session */
689    private class PruneTask extends AsyncTask<Void, Void, Void> {
690        @Override
691        protected Void doInBackground(Void... ignored) {
692            int numSessionsToRemove;
693
694            SparseArray<IBinder> sessionsToRemove;
695
696            synchronized (mLock) {
697                numSessionsToRemove = mSessions.size();
698                sessionsToRemove = new SparseArray<>(numSessionsToRemove);
699
700                for (int i = 0; i < numSessionsToRemove; i++) {
701                    Session session = mSessions.valueAt(i);
702
703                    sessionsToRemove.put(session.id, session.getActivityTokenLocked());
704                }
705            }
706
707            IActivityManager am = ActivityManager.getService();
708
709            // Only remove sessions which's activities are not known to the activity manager anymore
710            for (int i = 0; i < numSessionsToRemove; i++) {
711                try {
712                    // The activity manager cannot resolve activities that have been removed
713                    if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
714                        sessionsToRemove.removeAt(i);
715                        i--;
716                        numSessionsToRemove--;
717                    }
718                } catch (RemoteException e) {
719                    Slog.w(TAG, "Cannot figure out if activity is finished", e);
720                }
721            }
722
723            synchronized (mLock) {
724                for (int i = 0; i < numSessionsToRemove; i++) {
725                    Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
726
727                    if (sessionToRemove != null && sessionsToRemove.valueAt(i)
728                            == sessionToRemove.getActivityTokenLocked()) {
729                        if (sessionToRemove.isSavingLocked()) {
730                            if (sVerbose) {
731                                Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
732                            }
733                        } else {
734                            if (sDebug) {
735                                Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
736                                    + sessionToRemove.getActivityTokenLocked() + ")");
737                            }
738                            sessionToRemove.removeSelfLocked();
739                        }
740                    }
741                }
742            }
743
744            return null;
745        }
746    }
747}
748