2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.autofill;
19 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
20 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
21 import static android.view.autofill.AutofillManager.NO_SESSION;
23 import static com.android.server.autofill.Helper.sDebug;
24 import static com.android.server.autofill.Helper.sVerbose;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityManager;
29 import android.app.AppGlobals;
30 import android.app.IActivityManager;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.content.pm.ServiceInfo;
37 import android.graphics.Rect;
38 import android.graphics.drawable.Drawable;
39 import android.metrics.LogMaker;
40 import android.os.AsyncTask;
41 import android.os.Binder;
42 import android.os.Bundle;
43 import android.os.IBinder;
44 import android.os.Looper;
45 import android.os.RemoteCallbackList;
46 import android.os.RemoteException;
47 import android.os.UserHandle;
48 import android.os.UserManager;
49 import android.provider.Settings;
50 import android.service.autofill.AutofillService;
51 import android.service.autofill.AutofillServiceInfo;
52 import android.service.autofill.FillEventHistory;
53 import android.service.autofill.FillEventHistory.Event;
54 import android.service.autofill.FillResponse;
55 import android.service.autofill.IAutoFillService;
56 import android.text.TextUtils;
57 import android.util.ArraySet;
58 import android.util.DebugUtils;
59 import android.util.LocalLog;
60 import android.util.Slog;
61 import android.util.SparseArray;
62 import android.view.autofill.AutofillId;
63 import android.view.autofill.AutofillManager;
64 import android.view.autofill.AutofillValue;
65 import android.view.autofill.IAutoFillManagerClient;
67 import com.android.internal.R;
68 import com.android.internal.annotations.GuardedBy;
69 import com.android.internal.logging.MetricsLogger;
70 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
71 import com.android.internal.os.HandlerCaller;
72 import com.android.server.autofill.ui.AutoFillUI;
74 import java.io.PrintWriter;
75 import java.util.ArrayList;
76 import java.util.Random;
79 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
80 * app's {@link IAutoFillService} implementation.
83 final class AutofillManagerServiceImpl {
85 private static final String TAG = "AutofillManagerServiceImpl";
86 private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
88 /** Minimum interval to prune abandoned sessions */
89 private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
91 static final int MSG_SERVICE_SAVE = 1;
93 private final int mUserId;
94 private final Context mContext;
95 private final Object mLock;
96 private final AutoFillUI mUi;
97 private final MetricsLogger mMetricsLogger = new MetricsLogger();
99 private RemoteCallbackList<IAutoFillManagerClient> mClients;
100 private AutofillServiceInfo mInfo;
102 private static final Random sRandom = new Random();
104 private final LocalLog mRequestsHistory;
105 private final LocalLog mUiLatencyHistory;
108 * Whether service was disabled for user due to {@link UserManager} restrictions.
110 private boolean mDisabled;
113 * Caches whether the setup completed for the current user.
116 private boolean mSetupComplete;
118 private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
120 case MSG_SERVICE_SAVE:
121 handleSessionSave(msg.arg1);
124 Slog.w(TAG, "invalid msg on handler: " + msg);
128 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
129 mHandlerCallback, true);
132 * Cache of pending {@link Session}s, keyed by sessionId.
134 * <p>They're kept until the {@link AutofillService} finished handling a request, an error
135 * occurs, or the session is abandoned.
138 private final SparseArray<Session> mSessions = new SparseArray<>();
140 /** The last selection */
142 private FillEventHistory mEventHistory;
144 /** When was {@link PruneTask} last executed? */
145 private long mLastPrune = 0;
147 AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
148 LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
151 mRequestsHistory = requestsHistory;
152 mUiLatencyHistory = uiLatencyHistory;
155 updateLocked(disabled);
159 CharSequence getServiceName() {
160 final String packageName = getServicePackageName();
161 if (packageName == null) {
166 final PackageManager pm = mContext.getPackageManager();
167 final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
168 return pm.getApplicationLabel(info);
169 } catch (Exception e) {
170 Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
176 String getServicePackageName() {
177 final ComponentName serviceComponent = getServiceComponentName();
178 if (serviceComponent != null) {
179 return serviceComponent.getPackageName();
184 ComponentName getServiceComponentName() {
185 synchronized (mLock) {
189 return mInfo.getServiceInfo().getComponentName();
193 private boolean isSetupCompletedLocked() {
194 final String setupComplete = Settings.Secure.getStringForUser(
195 mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
196 return "1".equals(setupComplete);
199 private String getComponentNameFromSettings() {
200 return Settings.Secure.getStringForUser(
201 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
204 void updateLocked(boolean disabled) {
205 final boolean wasEnabled = isEnabled();
207 Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
208 + ", mSetupComplete= " + mSetupComplete
209 + ", disabled=" + disabled + ", mDisabled=" + mDisabled);
211 mSetupComplete = isSetupCompletedLocked();
212 mDisabled = disabled;
213 ComponentName serviceComponent = null;
214 ServiceInfo serviceInfo = null;
215 final String componentName = getComponentNameFromSettings();
216 if (!TextUtils.isEmpty(componentName)) {
218 serviceComponent = ComponentName.unflattenFromString(componentName);
219 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
221 } catch (RuntimeException | RemoteException e) {
222 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
227 if (serviceInfo != null) {
228 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
229 serviceComponent, mUserId);
230 if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
233 if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
235 final boolean isEnabled = isEnabled();
236 if (wasEnabled != isEnabled) {
238 final int sessionCount = mSessions.size();
239 for (int i = sessionCount - 1; i >= 0; i--) {
240 final Session session = mSessions.valueAt(i);
241 session.removeSelfLocked();
244 sendStateToClients(false);
246 } catch (Exception e) {
247 Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
251 boolean addClientLocked(IAutoFillManagerClient client) {
252 if (mClients == null) {
253 mClients = new RemoteCallbackList<>();
255 mClients.register(client);
259 void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
263 final Session session = mSessions.get(sessionId);
264 if (session != null && uid == session.uid) {
265 session.setAuthenticationResultLocked(data, authenticationId);
269 void setHasCallback(int sessionId, int uid, boolean hasIt) {
273 final Session session = mSessions.get(sessionId);
274 if (session != null && uid == session.uid) {
275 synchronized (mLock) {
276 session.setHasCallbackLocked(hasIt);
281 int startSessionLocked(@NonNull IBinder activityToken, int uid,
282 @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
283 @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
284 int flags, @NonNull ComponentName componentName) {
288 if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
290 // Occasionally clean up abandoned sessions
291 pruneAbandonedSessionsLocked();
293 final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
294 hasCallback, componentName);
295 if (newSession == null) {
299 final String historyItem =
300 "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
301 + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
302 hasCallback + " f=" + flags;
303 mRequestsHistory.log(historyItem);
305 newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
307 return newSession.id;
311 * Remove abandoned sessions if needed.
313 private void pruneAbandonedSessionsLocked() {
314 long now = System.currentTimeMillis();
315 if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
318 if (mSessions.size() > 0) {
319 (new PruneTask()).execute();
324 void finishSessionLocked(int sessionId, int uid) {
329 final Session session = mSessions.get(sessionId);
330 if (session == null || uid != session.uid) {
332 Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")");
337 final boolean finished = session.showSaveLocked();
338 if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
341 session.removeSelfLocked();
345 void cancelSessionLocked(int sessionId, int uid) {
350 final Session session = mSessions.get(sessionId);
351 if (session == null || uid != session.uid) {
352 Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
355 session.removeSelfLocked();
358 void disableOwnedAutofillServicesLocked(int uid) {
359 Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
360 if (mInfo == null) return;
362 final ServiceInfo serviceInfo = mInfo.getServiceInfo();
363 if (serviceInfo.applicationInfo.uid != uid) {
364 Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
365 + " instead of " + serviceInfo.applicationInfo.uid
366 + " for service " + mInfo);
371 final long identity = Binder.clearCallingIdentity();
373 final String autoFillService = getComponentNameFromSettings();
374 final ComponentName componentName = serviceInfo.getComponentName();
375 if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
376 mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
377 componentName.getPackageName());
378 Settings.Secure.putStringForUser(mContext.getContentResolver(),
379 Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
380 destroySessionsLocked();
382 Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
383 + serviceInfo + ") does not match Settings (" + autoFillService + ")");
386 Binder.restoreCallingIdentity(identity);
390 private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
391 @NonNull IBinder appCallbackToken, boolean hasCallback,
392 @NonNull ComponentName componentName) {
393 // use random ids so that one app cannot know that another app creates sessions
398 if (tries > MAX_SESSION_ID_CREATE_TRIES) {
399 Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
403 sessionId = sRandom.nextInt();
404 } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
406 assertCallerLocked(componentName);
408 final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
409 sessionId, uid, activityToken, appCallbackToken, hasCallback,
410 mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
411 mSessions.put(newSession.id, newSession);
417 * Asserts the component is owned by the caller.
419 private void assertCallerLocked(@NonNull ComponentName componentName) {
420 final PackageManager pm = mContext.getPackageManager();
421 final int callingUid = Binder.getCallingUid();
422 final int packageUid;
424 packageUid = pm.getPackageUidAsUser(componentName.getPackageName(),
425 UserHandle.getCallingUserId());
426 } catch (NameNotFoundException e) {
427 throw new SecurityException("Could not verify UID for " + componentName);
429 if (callingUid != packageUid) {
430 final String[] packages = pm.getPackagesForUid(callingUid);
431 final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
432 Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
433 + ") passed component (" + componentName + ") owned by UID " + packageUid);
434 mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT)
435 .setPackageName(callingPackage)
436 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
437 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
438 componentName == null ? "null" : componentName.flattenToShortString()));
439 throw new SecurityException("Invalid component: " + componentName);
444 * Restores a session after an activity was temporarily destroyed.
446 * @param sessionId The id of the session to restore
447 * @param uid UID of the process that tries to restore the session
448 * @param activityToken The new instance of the activity
449 * @param appCallback The callbacks to the activity
451 boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
452 @NonNull IBinder appCallback) {
453 final Session session = mSessions.get(sessionId);
455 if (session == null || uid != session.uid) {
458 session.switchActivity(activityToken, appCallback);
464 * Updates a session and returns whether it should be restarted.
466 boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
467 AutofillValue value, int action, int flags) {
468 final Session session = mSessions.get(sessionId);
469 if (session == null || session.uid != uid) {
470 if ((flags & FLAG_MANUAL_REQUEST) != 0) {
472 Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
478 Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
484 session.updateLocked(autofillId, virtualBounds, value, action, flags);
488 void removeSessionLocked(int sessionId) {
489 mSessions.remove(sessionId);
492 private void handleSessionSave(int sessionId) {
493 synchronized (mLock) {
494 final Session session = mSessions.get(sessionId);
495 if (session == null) {
496 Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
500 session.callSaveLocked();
504 void onPendingSaveUi(int operation, @NonNull IBinder token) {
505 if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
506 synchronized (mLock) {
507 final int sessionCount = mSessions.size();
508 for (int i = sessionCount - 1; i >= 0; i--) {
509 final Session session = mSessions.valueAt(i);
510 if (session.isSaveUiPendingForTokenLocked(token)) {
511 session.onPendingSaveUi(operation, token);
517 Slog.d(TAG, "No pending Save UI for token " + token + " and operation "
518 + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_",
523 void destroyLocked() {
524 if (sVerbose) Slog.v(TAG, "destroyLocked()");
526 final int numSessions = mSessions.size();
527 final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
528 for (int i = 0; i < numSessions; i++) {
529 final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
530 if (remoteFillService != null) {
531 remoteFillServices.add(remoteFillService);
535 for (int i = 0; i < remoteFillServices.size(); i++) {
536 remoteFillServices.valueAt(i).destroy();
539 sendStateToClients(true);
543 CharSequence getServiceLabel() {
544 return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
548 Drawable getServiceIcon() {
549 return mInfo.getServiceInfo().loadIcon(mContext.getPackageManager());
553 * Initializes the last fill selection after an autofill service returned a new
554 * {@link FillResponse}.
556 void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
557 synchronized (mLock) {
558 mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
563 * Resets the last fill selection.
565 void resetLastResponse() {
566 synchronized (mLock) {
567 mEventHistory = null;
571 private boolean isValidEventLocked(String method, int sessionId) {
572 if (mEventHistory == null) {
573 Slog.w(TAG, method + ": not logging event because history is null");
576 if (sessionId != mEventHistory.getSessionId()) {
578 Slog.d(TAG, method + ": not logging event for session " + sessionId
579 + " because tracked session is " + mEventHistory.getSessionId());
587 * Updates the last fill selection when an authentication was selected.
589 void setAuthenticationSelected(int sessionId) {
590 synchronized (mLock) {
591 if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
592 mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
598 * Updates the last fill selection when an dataset authentication was selected.
600 void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
601 synchronized (mLock) {
602 if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
603 mEventHistory.addEvent(
604 new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
610 * Updates the last fill selection when an save Ui is shown.
612 void logSaveShown(int sessionId) {
613 synchronized (mLock) {
614 if (isValidEventLocked("logSaveShown()", sessionId)) {
615 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
621 * Updates the last fill response when a dataset was selected.
623 void logDatasetSelected(@Nullable String selectedDataset, int sessionId) {
624 synchronized (mLock) {
625 if (isValidEventLocked("setDatasetSelected()", sessionId)) {
626 mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
632 * Gets the fill event history.
634 * @param callingUid The calling uid
636 * @return The history or {@code null} if there is none.
638 FillEventHistory getFillEventHistory(int callingUid) {
639 synchronized (mLock) {
640 if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
641 return mEventHistory;
648 void dumpLocked(String prefix, PrintWriter pw) {
649 final String prefix2 = prefix + " ";
651 pw.print(prefix); pw.print("User: "); pw.println(mUserId);
652 pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
653 ? mInfo.getServiceInfo().getComponentName() : null);
654 pw.print(prefix); pw.print("Component from settings: ");
655 pw.println(getComponentNameFromSettings());
656 pw.print(prefix); pw.print("Default component: ");
657 pw.println(mContext.getString(R.string.config_defaultAutofillService));
658 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
659 pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
660 pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
662 final int size = mSessions.size();
664 pw.print(prefix); pw.println("No sessions");
666 pw.print(prefix); pw.print(size); pw.println(" sessions:");
667 for (int i = 0; i < size; i++) {
668 pw.print(prefix); pw.print("#"); pw.println(i + 1);
669 mSessions.valueAt(i).dumpLocked(prefix2, pw);
673 if (mEventHistory == null || mEventHistory.getEvents() == null
674 || mEventHistory.getEvents().size() == 0) {
675 pw.print(prefix); pw.println("No event on last fill response");
677 pw.print(prefix); pw.println("Events of last fill response:");
680 int numEvents = mEventHistory.getEvents().size();
681 for (int i = 0; i < numEvents; i++) {
682 final Event event = mEventHistory.getEvents().get(i);
683 pw.println(" " + i + ": eventType=" + event.getType() + " datasetId="
684 + event.getDatasetId());
689 void destroySessionsLocked() {
690 if (mSessions.size() == 0) {
691 mUi.destroyAll(null, null, false);
694 while (mSessions.size() > 0) {
695 mSessions.valueAt(0).forceRemoveSelfLocked();
699 // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
700 void destroyFinishedSessionsLocked() {
701 final int sessionCount = mSessions.size();
702 for (int i = sessionCount - 1; i >= 0; i--) {
703 final Session session = mSessions.valueAt(i);
704 if (session.isSavingLocked()) {
705 if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
706 session.forceRemoveSelfLocked();
711 void listSessionsLocked(ArrayList<String> output) {
712 final int numSessions = mSessions.size();
713 for (int i = 0; i < numSessions; i++) {
714 output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
715 : null) + ":" + mSessions.keyAt(i));
719 private void sendStateToClients(boolean resetClient) {
720 final RemoteCallbackList<IAutoFillManagerClient> clients;
721 final int userClientCount;
722 synchronized (mLock) {
723 if (mClients == null) {
727 userClientCount = clients.beginBroadcast();
730 for (int i = 0; i < userClientCount; i++) {
731 final IAutoFillManagerClient client = clients.getBroadcastItem(i);
733 final boolean resetSession;
734 synchronized (mLock) {
735 resetSession = resetClient || isClientSessionDestroyedLocked(client);
737 client.setState(isEnabled(), resetSession, resetClient);
738 } catch (RemoteException re) {
743 clients.finishBroadcast();
747 private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
748 final int sessionCount = mSessions.size();
749 for (int i = 0; i < sessionCount; i++) {
750 final Session session = mSessions.valueAt(i);
751 if (session.getClient().equals(client)) {
752 return session.isDestroyed();
758 boolean isEnabled() {
759 return mSetupComplete && mInfo != null && !mDisabled;
763 public String toString() {
764 return "AutofillManagerServiceImpl: [userId=" + mUserId
765 + ", component=" + (mInfo != null
766 ? mInfo.getServiceInfo().getComponentName() : null) + "]";
769 /** Task used to prune abandoned session */
770 private class PruneTask extends AsyncTask<Void, Void, Void> {
772 protected Void doInBackground(Void... ignored) {
773 int numSessionsToRemove;
775 SparseArray<IBinder> sessionsToRemove;
777 synchronized (mLock) {
778 numSessionsToRemove = mSessions.size();
779 sessionsToRemove = new SparseArray<>(numSessionsToRemove);
781 for (int i = 0; i < numSessionsToRemove; i++) {
782 Session session = mSessions.valueAt(i);
784 sessionsToRemove.put(session.id, session.getActivityTokenLocked());
788 IActivityManager am = ActivityManager.getService();
790 // Only remove sessions which's activities are not known to the activity manager anymore
791 for (int i = 0; i < numSessionsToRemove; i++) {
793 // The activity manager cannot resolve activities that have been removed
794 if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
795 sessionsToRemove.removeAt(i);
797 numSessionsToRemove--;
799 } catch (RemoteException e) {
800 Slog.w(TAG, "Cannot figure out if activity is finished", e);
804 synchronized (mLock) {
805 for (int i = 0; i < numSessionsToRemove; i++) {
806 Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
808 if (sessionToRemove != null && sessionsToRemove.valueAt(i)
809 == sessionToRemove.getActivityTokenLocked()) {
810 if (sessionToRemove.isSavingLocked()) {
812 Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
816 Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
817 + sessionToRemove.getActivityTokenLocked() + ")");
819 sessionToRemove.removeSelfLocked();