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.ActivityManagerInternal;
30 import android.app.AppGlobals;
31 import android.app.IActivityManager;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.pm.ServiceInfo;
38 import android.graphics.Rect;
39 import android.graphics.drawable.Drawable;
40 import android.metrics.LogMaker;
41 import android.os.AsyncTask;
42 import android.os.Binder;
43 import android.os.Bundle;
44 import android.os.IBinder;
45 import android.os.Looper;
46 import android.os.RemoteCallbackList;
47 import android.os.RemoteException;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.provider.Settings;
51 import android.service.autofill.AutofillService;
52 import android.service.autofill.AutofillServiceInfo;
53 import android.service.autofill.FillEventHistory;
54 import android.service.autofill.FillEventHistory.Event;
55 import android.service.autofill.FillResponse;
56 import android.service.autofill.IAutoFillService;
57 import android.text.TextUtils;
58 import android.util.ArraySet;
59 import android.util.DebugUtils;
60 import android.util.LocalLog;
61 import android.util.Slog;
62 import android.util.SparseArray;
63 import android.view.autofill.AutofillId;
64 import android.view.autofill.AutofillManager;
65 import android.view.autofill.AutofillValue;
66 import android.view.autofill.IAutoFillManagerClient;
68 import com.android.internal.R;
69 import com.android.internal.annotations.GuardedBy;
70 import com.android.internal.logging.MetricsLogger;
71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
72 import com.android.internal.os.HandlerCaller;
73 import com.android.server.LocalServices;
74 import com.android.server.autofill.ui.AutoFillUI;
76 import java.io.PrintWriter;
77 import java.util.ArrayList;
78 import java.util.Random;
81 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
82 * app's {@link IAutoFillService} implementation.
85 final class AutofillManagerServiceImpl {
87 private static final String TAG = "AutofillManagerServiceImpl";
88 private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
90 /** Minimum interval to prune abandoned sessions */
91 private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
93 static final int MSG_SERVICE_SAVE = 1;
95 private final int mUserId;
96 private final Context mContext;
97 private final Object mLock;
98 private final AutoFillUI mUi;
99 private final MetricsLogger mMetricsLogger = new MetricsLogger();
101 private RemoteCallbackList<IAutoFillManagerClient> mClients;
102 private AutofillServiceInfo mInfo;
104 private static final Random sRandom = new Random();
106 private final LocalLog mRequestsHistory;
107 private final LocalLog mUiLatencyHistory;
110 * Whether service was disabled for user due to {@link UserManager} restrictions.
112 private boolean mDisabled;
115 * Caches whether the setup completed for the current user.
118 private boolean mSetupComplete;
120 private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
122 case MSG_SERVICE_SAVE:
123 handleSessionSave(msg.arg1);
126 Slog.w(TAG, "invalid msg on handler: " + msg);
130 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
131 mHandlerCallback, true);
134 * Cache of pending {@link Session}s, keyed by sessionId.
136 * <p>They're kept until the {@link AutofillService} finished handling a request, an error
137 * occurs, or the session is abandoned.
140 private final SparseArray<Session> mSessions = new SparseArray<>();
142 /** The last selection */
144 private FillEventHistory mEventHistory;
146 /** When was {@link PruneTask} last executed? */
147 private long mLastPrune = 0;
149 AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
150 LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
153 mRequestsHistory = requestsHistory;
154 mUiLatencyHistory = uiLatencyHistory;
157 updateLocked(disabled);
161 CharSequence getServiceName() {
162 final String packageName = getServicePackageName();
163 if (packageName == null) {
168 final PackageManager pm = mContext.getPackageManager();
169 final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
170 return pm.getApplicationLabel(info);
171 } catch (Exception e) {
172 Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
178 String getServicePackageName() {
179 final ComponentName serviceComponent = getServiceComponentName();
180 if (serviceComponent != null) {
181 return serviceComponent.getPackageName();
186 ComponentName getServiceComponentName() {
187 synchronized (mLock) {
191 return mInfo.getServiceInfo().getComponentName();
195 private boolean isSetupCompletedLocked() {
196 final String setupComplete = Settings.Secure.getStringForUser(
197 mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
198 return "1".equals(setupComplete);
201 private String getComponentNameFromSettings() {
202 return Settings.Secure.getStringForUser(
203 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
206 void updateLocked(boolean disabled) {
207 final boolean wasEnabled = isEnabled();
209 Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
210 + ", mSetupComplete= " + mSetupComplete
211 + ", disabled=" + disabled + ", mDisabled=" + mDisabled);
213 mSetupComplete = isSetupCompletedLocked();
214 mDisabled = disabled;
215 ComponentName serviceComponent = null;
216 ServiceInfo serviceInfo = null;
217 final String componentName = getComponentNameFromSettings();
218 if (!TextUtils.isEmpty(componentName)) {
220 serviceComponent = ComponentName.unflattenFromString(componentName);
221 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
223 } catch (RuntimeException | RemoteException e) {
224 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
229 if (serviceInfo != null) {
230 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
231 serviceComponent, mUserId);
232 if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
235 if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
237 final boolean isEnabled = isEnabled();
238 if (wasEnabled != isEnabled) {
240 final int sessionCount = mSessions.size();
241 for (int i = sessionCount - 1; i >= 0; i--) {
242 final Session session = mSessions.valueAt(i);
243 session.removeSelfLocked();
246 sendStateToClients(false);
248 } catch (Exception e) {
249 Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
253 boolean addClientLocked(IAutoFillManagerClient client) {
254 if (mClients == null) {
255 mClients = new RemoteCallbackList<>();
257 mClients.register(client);
261 void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
265 final Session session = mSessions.get(sessionId);
266 if (session != null && uid == session.uid) {
267 session.setAuthenticationResultLocked(data, authenticationId);
271 void setHasCallback(int sessionId, int uid, boolean hasIt) {
275 final Session session = mSessions.get(sessionId);
276 if (session != null && uid == session.uid) {
277 synchronized (mLock) {
278 session.setHasCallbackLocked(hasIt);
283 int startSessionLocked(@NonNull IBinder activityToken, int uid,
284 @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
285 @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
286 int flags, @NonNull ComponentName componentName) {
290 if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
292 // Occasionally clean up abandoned sessions
293 pruneAbandonedSessionsLocked();
295 final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
296 hasCallback, componentName);
297 if (newSession == null) {
301 final String historyItem =
302 "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
303 + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
304 hasCallback + " f=" + flags;
305 mRequestsHistory.log(historyItem);
307 newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
309 return newSession.id;
313 * Remove abandoned sessions if needed.
315 private void pruneAbandonedSessionsLocked() {
316 long now = System.currentTimeMillis();
317 if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
320 if (mSessions.size() > 0) {
321 (new PruneTask()).execute();
326 void finishSessionLocked(int sessionId, int uid) {
331 final Session session = mSessions.get(sessionId);
332 if (session == null || uid != session.uid) {
334 Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")");
339 final boolean finished = session.showSaveLocked();
340 if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
343 session.removeSelfLocked();
347 void cancelSessionLocked(int sessionId, int uid) {
352 final Session session = mSessions.get(sessionId);
353 if (session == null || uid != session.uid) {
354 Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
357 session.removeSelfLocked();
360 void disableOwnedAutofillServicesLocked(int uid) {
361 Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
362 if (mInfo == null) return;
364 final ServiceInfo serviceInfo = mInfo.getServiceInfo();
365 if (serviceInfo.applicationInfo.uid != uid) {
366 Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
367 + " instead of " + serviceInfo.applicationInfo.uid
368 + " for service " + mInfo);
373 final long identity = Binder.clearCallingIdentity();
375 final String autoFillService = getComponentNameFromSettings();
376 final ComponentName componentName = serviceInfo.getComponentName();
377 if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
378 mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
379 componentName.getPackageName());
380 Settings.Secure.putStringForUser(mContext.getContentResolver(),
381 Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
382 destroySessionsLocked();
384 Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
385 + serviceInfo + ") does not match Settings (" + autoFillService + ")");
388 Binder.restoreCallingIdentity(identity);
392 private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
393 @NonNull IBinder appCallbackToken, boolean hasCallback,
394 @NonNull ComponentName componentName) {
395 // use random ids so that one app cannot know that another app creates sessions
400 if (tries > MAX_SESSION_ID_CREATE_TRIES) {
401 Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
405 sessionId = sRandom.nextInt();
406 } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
408 assertCallerLocked(componentName);
410 final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
411 sessionId, uid, activityToken, appCallbackToken, hasCallback,
412 mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
413 mSessions.put(newSession.id, newSession);
419 * Asserts the component is owned by the caller.
421 private void assertCallerLocked(@NonNull ComponentName componentName) {
422 final String packageName = componentName.getPackageName();
423 final PackageManager pm = mContext.getPackageManager();
424 final int callingUid = Binder.getCallingUid();
425 final int packageUid;
427 packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
428 } catch (NameNotFoundException e) {
429 throw new SecurityException("Could not verify UID for " + componentName);
431 if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
432 .hasRunningActivity(callingUid, packageName)) {
433 final String[] packages = pm.getPackagesForUid(callingUid);
434 final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
435 Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
436 + ") passed component (" + componentName + ") owned by UID " + packageUid);
437 mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT)
438 .setPackageName(callingPackage)
439 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
440 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
441 componentName == null ? "null" : componentName.flattenToShortString()));
442 throw new SecurityException("Invalid component: " + componentName);
447 * Restores a session after an activity was temporarily destroyed.
449 * @param sessionId The id of the session to restore
450 * @param uid UID of the process that tries to restore the session
451 * @param activityToken The new instance of the activity
452 * @param appCallback The callbacks to the activity
454 boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
455 @NonNull IBinder appCallback) {
456 final Session session = mSessions.get(sessionId);
458 if (session == null || uid != session.uid) {
461 session.switchActivity(activityToken, appCallback);
467 * Updates a session and returns whether it should be restarted.
469 boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
470 AutofillValue value, int action, int flags) {
471 final Session session = mSessions.get(sessionId);
472 if (session == null || session.uid != uid) {
473 if ((flags & FLAG_MANUAL_REQUEST) != 0) {
475 Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
481 Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
487 session.updateLocked(autofillId, virtualBounds, value, action, flags);
491 void removeSessionLocked(int sessionId) {
492 mSessions.remove(sessionId);
495 private void handleSessionSave(int sessionId) {
496 synchronized (mLock) {
497 final Session session = mSessions.get(sessionId);
498 if (session == null) {
499 Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
503 session.callSaveLocked();
507 void onPendingSaveUi(int operation, @NonNull IBinder token) {
508 if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
509 synchronized (mLock) {
510 final int sessionCount = mSessions.size();
511 for (int i = sessionCount - 1; i >= 0; i--) {
512 final Session session = mSessions.valueAt(i);
513 if (session.isSaveUiPendingForTokenLocked(token)) {
514 session.onPendingSaveUi(operation, token);
520 Slog.d(TAG, "No pending Save UI for token " + token + " and operation "
521 + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_",
526 void destroyLocked() {
527 if (sVerbose) Slog.v(TAG, "destroyLocked()");
529 final int numSessions = mSessions.size();
530 final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
531 for (int i = 0; i < numSessions; i++) {
532 final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
533 if (remoteFillService != null) {
534 remoteFillServices.add(remoteFillService);
538 for (int i = 0; i < remoteFillServices.size(); i++) {
539 remoteFillServices.valueAt(i).destroy();
542 sendStateToClients(true);
546 CharSequence getServiceLabel() {
547 return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
551 Drawable getServiceIcon() {
552 return mInfo.getServiceInfo().loadIcon(mContext.getPackageManager());
556 * Initializes the last fill selection after an autofill service returned a new
557 * {@link FillResponse}.
559 void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
560 synchronized (mLock) {
561 mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
566 * Resets the last fill selection.
568 void resetLastResponse() {
569 synchronized (mLock) {
570 mEventHistory = null;
574 private boolean isValidEventLocked(String method, int sessionId) {
575 if (mEventHistory == null) {
576 Slog.w(TAG, method + ": not logging event because history is null");
579 if (sessionId != mEventHistory.getSessionId()) {
581 Slog.d(TAG, method + ": not logging event for session " + sessionId
582 + " because tracked session is " + mEventHistory.getSessionId());
590 * Updates the last fill selection when an authentication was selected.
592 void setAuthenticationSelected(int sessionId) {
593 synchronized (mLock) {
594 if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
595 mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
601 * Updates the last fill selection when an dataset authentication was selected.
603 void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
604 synchronized (mLock) {
605 if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
606 mEventHistory.addEvent(
607 new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
613 * Updates the last fill selection when an save Ui is shown.
615 void logSaveShown(int sessionId) {
616 synchronized (mLock) {
617 if (isValidEventLocked("logSaveShown()", sessionId)) {
618 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
624 * Updates the last fill response when a dataset was selected.
626 void logDatasetSelected(@Nullable String selectedDataset, int sessionId) {
627 synchronized (mLock) {
628 if (isValidEventLocked("setDatasetSelected()", sessionId)) {
629 mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
635 * Gets the fill event history.
637 * @param callingUid The calling uid
639 * @return The history or {@code null} if there is none.
641 FillEventHistory getFillEventHistory(int callingUid) {
642 synchronized (mLock) {
643 if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
644 return mEventHistory;
651 void dumpLocked(String prefix, PrintWriter pw) {
652 final String prefix2 = prefix + " ";
654 pw.print(prefix); pw.print("User: "); pw.println(mUserId);
655 pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
656 ? mInfo.getServiceInfo().getComponentName() : null);
657 pw.print(prefix); pw.print("Component from settings: ");
658 pw.println(getComponentNameFromSettings());
659 pw.print(prefix); pw.print("Default component: ");
660 pw.println(mContext.getString(R.string.config_defaultAutofillService));
661 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
662 pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
663 pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
665 final int size = mSessions.size();
667 pw.print(prefix); pw.println("No sessions");
669 pw.print(prefix); pw.print(size); pw.println(" sessions:");
670 for (int i = 0; i < size; i++) {
671 pw.print(prefix); pw.print("#"); pw.println(i + 1);
672 mSessions.valueAt(i).dumpLocked(prefix2, pw);
676 if (mEventHistory == null || mEventHistory.getEvents() == null
677 || mEventHistory.getEvents().size() == 0) {
678 pw.print(prefix); pw.println("No event on last fill response");
680 pw.print(prefix); pw.println("Events of last fill response:");
683 int numEvents = mEventHistory.getEvents().size();
684 for (int i = 0; i < numEvents; i++) {
685 final Event event = mEventHistory.getEvents().get(i);
686 pw.println(" " + i + ": eventType=" + event.getType() + " datasetId="
687 + event.getDatasetId());
692 void destroySessionsLocked() {
693 if (mSessions.size() == 0) {
694 mUi.destroyAll(null, null, false);
697 while (mSessions.size() > 0) {
698 mSessions.valueAt(0).forceRemoveSelfLocked();
702 // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
703 void destroyFinishedSessionsLocked() {
704 final int sessionCount = mSessions.size();
705 for (int i = sessionCount - 1; i >= 0; i--) {
706 final Session session = mSessions.valueAt(i);
707 if (session.isSavingLocked()) {
708 if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
709 session.forceRemoveSelfLocked();
714 void listSessionsLocked(ArrayList<String> output) {
715 final int numSessions = mSessions.size();
716 for (int i = 0; i < numSessions; i++) {
717 output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
718 : null) + ":" + mSessions.keyAt(i));
722 private void sendStateToClients(boolean resetClient) {
723 final RemoteCallbackList<IAutoFillManagerClient> clients;
724 final int userClientCount;
725 synchronized (mLock) {
726 if (mClients == null) {
730 userClientCount = clients.beginBroadcast();
733 for (int i = 0; i < userClientCount; i++) {
734 final IAutoFillManagerClient client = clients.getBroadcastItem(i);
736 final boolean resetSession;
737 synchronized (mLock) {
738 resetSession = resetClient || isClientSessionDestroyedLocked(client);
740 client.setState(isEnabled(), resetSession, resetClient);
741 } catch (RemoteException re) {
746 clients.finishBroadcast();
750 private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
751 final int sessionCount = mSessions.size();
752 for (int i = 0; i < sessionCount; i++) {
753 final Session session = mSessions.valueAt(i);
754 if (session.getClient().equals(client)) {
755 return session.isDestroyed();
761 boolean isEnabled() {
762 return mSetupComplete && mInfo != null && !mDisabled;
766 public String toString() {
767 return "AutofillManagerServiceImpl: [userId=" + mUserId
768 + ", component=" + (mInfo != null
769 ? mInfo.getServiceInfo().getComponentName() : null) + "]";
772 /** Task used to prune abandoned session */
773 private class PruneTask extends AsyncTask<Void, Void, Void> {
775 protected Void doInBackground(Void... ignored) {
776 int numSessionsToRemove;
778 SparseArray<IBinder> sessionsToRemove;
780 synchronized (mLock) {
781 numSessionsToRemove = mSessions.size();
782 sessionsToRemove = new SparseArray<>(numSessionsToRemove);
784 for (int i = 0; i < numSessionsToRemove; i++) {
785 Session session = mSessions.valueAt(i);
787 sessionsToRemove.put(session.id, session.getActivityTokenLocked());
791 IActivityManager am = ActivityManager.getService();
793 // Only remove sessions which's activities are not known to the activity manager anymore
794 for (int i = 0; i < numSessionsToRemove; i++) {
796 // The activity manager cannot resolve activities that have been removed
797 if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
798 sessionsToRemove.removeAt(i);
800 numSessionsToRemove--;
802 } catch (RemoteException e) {
803 Slog.w(TAG, "Cannot figure out if activity is finished", e);
807 synchronized (mLock) {
808 for (int i = 0; i < numSessionsToRemove; i++) {
809 Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
811 if (sessionToRemove != null && sessionsToRemove.valueAt(i)
812 == sessionToRemove.getActivityTokenLocked()) {
813 if (sessionToRemove.isSavingLocked()) {
815 Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
819 Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
820 + sessionToRemove.getActivityTokenLocked() + ")");
822 sessionToRemove.removeSelfLocked();