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