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