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