All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.autofill.AutofillManagerServiceImpl Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.autofill;

import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED;
import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
import static android.view.autofill.AutofillManager.NO_SESSION;
import static android.view.autofill.AutofillManager.RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY;

import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FillEventHistory;
import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
import android.service.autofill.SaveInfo;
import android.service.autofill.UserData;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LocalLog;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
 * app's {@link IAutoFillService} implementation.
 *
 */
final class AutofillManagerServiceImpl
        extends AbstractPerUserSystemService {

    private static final String TAG = "AutofillManagerServiceImpl";
    private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;

    /** Minimum interval to prune abandoned sessions */
    private static final int MAX_ABANDONED_SESSION_MILLIS = 30_000;

    private final AutoFillUI mUi;
    private final MetricsLogger mMetricsLogger = new MetricsLogger();

    @GuardedBy("mLock")
    private RemoteCallbackList mClients;

    @GuardedBy("mLock")
    private AutofillServiceInfo mInfo;

    private static final Random sRandom = new Random();

    private final LocalLog mUiLatencyHistory;
    private final LocalLog mWtfHistory;
    private final FieldClassificationStrategy mFieldClassificationStrategy;

    /**
     * Apps disabled by the service; key is package name, value is when they will be enabled again.
     */
    @GuardedBy("mLock")
    private ArrayMap mDisabledApps;

    /**
     * Activities disabled by the service; key is component name, value is when they will be enabled
     * again.
     */
    @GuardedBy("mLock")
    private ArrayMap mDisabledActivities;

    /**
     * Data used for field classification.
     */
    @GuardedBy("mLock")
    private UserData mUserData;

    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);

    /**
     * Cache of pending {@link Session}s, keyed by sessionId.
     *
     * 

They're kept until the {@link AutofillService} finished handling a request, an error * occurs, or the session is abandoned. */ @GuardedBy("mLock") private final SparseArray mSessions = new SparseArray<>(); /** The last selection */ @GuardedBy("mLock") private FillEventHistory mEventHistory; /** Shared instance, doesn't need to be logged */ private final AutofillCompatState mAutofillCompatState; /** When was {@link PruneTask} last executed? */ private long mLastPrune = 0; /** * Reference to the {@link RemoteAugmentedAutofillService}, is set on demand. */ @GuardedBy("mLock") @Nullable private RemoteAugmentedAutofillService mRemoteAugmentedAutofillService; @GuardedBy("mLock") @Nullable private ServiceInfo mRemoteAugmentedAutofillServiceInfo; AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, boolean disabled) { super(master, lock, userId); mUiLatencyHistory = uiLatencyHistory; mWtfHistory = wtfHistory; mUi = ui; mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); mAutofillCompatState = autofillCompatState; updateLocked(disabled); } @GuardedBy("mLock") void onBackKeyPressed() { final RemoteAugmentedAutofillService remoteService = getRemoteAugmentedAutofillServiceLocked(); if (remoteService != null) { remoteService.onDestroyAutofillWindowsRequest(); } } @GuardedBy("mLock") @Override // from PerUserSystemService protected boolean updateLocked(boolean disabled) { destroySessionsLocked(); final boolean enabledChanged = super.updateLocked(disabled); if (enabledChanged) { if (!isEnabledLocked()) { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { final Session session = mSessions.valueAt(i); session.removeSelfLocked(); } } sendStateToClients(/* resetClient= */ false); } updateRemoteAugmentedAutofillService(); return enabledChanged; } @Override // from PerUserSystemService protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) throws NameNotFoundException { mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId); return mInfo.getServiceInfo(); } @Nullable String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) { return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId); } /** * Adds the client and return the proper flags * * @return {@code 0} if disabled, {@code FLAG_ADD_CLIENT_ENABLED} if enabled (it might be * OR'ed with {@code FLAG_AUGMENTED_AUTOFILL_REQUEST}). */ @GuardedBy("mLock") int addClientLocked(IAutoFillManagerClient client, ComponentName componentName) { if (mClients == null) { mClients = new RemoteCallbackList<>(); } mClients.register(client); if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED; // Check if it's enabled for augmented autofill if (isAugmentedAutofillServiceAvailableLocked() && isWhitelistedForAugmentedAutofillLocked(componentName)) { return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; } // No flags / disabled return 0; } @GuardedBy("mLock") void removeClientLocked(IAutoFillManagerClient client) { if (mClients != null) { mClients.unregister(client); } } @GuardedBy("mLock") void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) { if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); if (session != null && uid == session.uid) { session.setAuthenticationResultLocked(data, authenticationId); } } void setHasCallback(int sessionId, int uid, boolean hasIt) { if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); if (session != null && uid == session.uid) { synchronized (mLock) { session.setHasCallbackLocked(hasIt); } } } /** * Starts a new session. * * @return {@code long} whose right-most 32 bits represent the session id (which is always * non-negative), and the left-most contains extra flags (currently either {@code 0} or * {@link AutofillManager#RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY}). */ @GuardedBy("mLock") long startSessionLocked(@NonNull IBinder activityToken, int taskId, int uid, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, @NonNull ComponentName componentName, boolean compatMode, boolean bindInstantServiceAllowed, int flags) { // FLAG_AUGMENTED_AUTOFILL_REQUEST is set in the flags when standard autofill is disabled // but the package is whitelisted for augmented autofill boolean forAugmentedAutofillOnly = (flags & FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY) != 0; if (!isEnabledLocked() && !forAugmentedAutofillOnly) { return 0; } if (!forAugmentedAutofillOnly && isAutofillDisabledLocked(componentName)) { // Standard autofill is enabled, but service disabled autofill for this activity; that // means no session, unless the activity is whitelisted for augmented autofill if (isWhitelistedForAugmentedAutofillLocked(componentName)) { if (sDebug) { Slog.d(TAG, "startSession(" + componentName + "): disabled by service but " + "whitelisted for augmented autofill"); } forAugmentedAutofillOnly = true; } else { if (sDebug) { Slog.d(TAG, "startSession(" + componentName + "): ignored because " + "disabled by service and not whitelisted for augmented autofill"); } final IAutoFillManagerClient client = IAutoFillManagerClient.Stub .asInterface(appCallbackToken); try { client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, /* autofillableIds= */ null); } catch (RemoteException e) { Slog.w(TAG, "Could not notify " + componentName + " that it's disabled: " + e); } return NO_SESSION; } } if (sVerbose) { Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags + ", forAugmentedAutofillOnly=" + forAugmentedAutofillOnly); } // Occasionally clean up abandoned sessions pruneAbandonedSessionsLocked(); final Session newSession = createSessionByTokenLocked(activityToken, taskId, uid, appCallbackToken, hasCallback, componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly, flags); if (newSession == null) { return NO_SESSION; } // Service can be null when it's only for augmented autofill String servicePackageName = mInfo == null ? null : mInfo.getServiceInfo().packageName; final String historyItem = "id=" + newSession.id + " uid=" + uid + " a=" + componentName.toShortString() + " s=" + servicePackageName + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly; mMaster.logRequestLocked(historyItem); newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); if (forAugmentedAutofillOnly) { // Must embed the flag in the response, at the high-end side of the long. // (session is always positive, so we don't have to worry about the signal bit) final long extraFlags = ((long) RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) << 32; final long result = extraFlags | newSession.id; return result; } else { return newSession.id; } } /** * Remove abandoned sessions if needed. */ @GuardedBy("mLock") private void pruneAbandonedSessionsLocked() { long now = System.currentTimeMillis(); if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) { mLastPrune = now; if (mSessions.size() > 0) { (new PruneTask()).execute(); } } } @GuardedBy("mLock") void setAutofillFailureLocked(int sessionId, int uid, @NonNull List ids) { if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); if (session == null || uid != session.uid) { Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")"); return; } session.setAutofillFailureLocked(ids); } @GuardedBy("mLock") void finishSessionLocked(int sessionId, int uid) { if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); if (session == null || uid != session.uid) { if (sVerbose) { Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")"); } return; } session.logContextCommitted(); final boolean finished = session.showSaveLocked(); if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished); if (finished) { session.removeSelfLocked(); } } @GuardedBy("mLock") void cancelSessionLocked(int sessionId, int uid) { if (!isEnabledLocked()) { return; } final Session session = mSessions.get(sessionId); if (session == null || uid != session.uid) { Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")"); return; } session.removeSelfLocked(); } @GuardedBy("mLock") void disableOwnedAutofillServicesLocked(int uid) { Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo); if (mInfo == null) return; final ServiceInfo serviceInfo = mInfo.getServiceInfo(); if (serviceInfo.applicationInfo.uid != uid) { Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid + " instead of " + serviceInfo.applicationInfo.uid + " for service " + mInfo); return; } final long identity = Binder.clearCallingIdentity(); try { final String autoFillService = getComponentNameLocked(); final ComponentName componentName = serviceInfo.getComponentName(); if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) { mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF, componentName.getPackageName()); Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, null, mUserId); destroySessionsLocked(); } else { Slog.w(TAG, "disableOwnedServices(): ignored because current service (" + serviceInfo + ") does not match Settings (" + autoFillService + ")"); } } finally { Binder.restoreCallingIdentity(identity); } } @GuardedBy("mLock") private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int taskId, int uid, @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull ComponentName componentName, boolean compatMode, boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) { // use random ids so that one app cannot know that another app creates sessions int sessionId; int tries = 0; do { tries++; if (tries > MAX_SESSION_ID_CREATE_TRIES) { Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries"); return null; } sessionId = Math.abs(sRandom.nextInt()); } while (sessionId == 0 || sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); assertCallerLocked(componentName, compatMode); // It's null when the session is just for augmented autofill final ComponentName serviceComponentName = mInfo == null ? null : mInfo.getServiceInfo().getComponentName(); final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory, mWtfHistory, serviceComponentName, componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly, flags); mSessions.put(newSession.id, newSession); return newSession; } /** * Asserts the component is owned by the caller. */ private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) { final String packageName = componentName.getPackageName(); final PackageManager pm = getContext().getPackageManager(); final int callingUid = Binder.getCallingUid(); final int packageUid; try { packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); } catch (NameNotFoundException e) { throw new SecurityException("Could not verify UID for " + componentName); } if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) .hasRunningActivity(callingUid, packageName)) { final String[] packages = pm.getPackagesForUid(callingUid); final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid + ") passed component (" + componentName + ") owned by UID " + packageUid); // NOTE: not using Helper.newLogMaker() because we don't have the session id final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT) .setPackageName(callingPackage) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName()) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, componentName == null ? "null" : componentName.flattenToShortString()); if (compatMode) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); } mMetricsLogger.write(log); throw new SecurityException("Invalid component: " + componentName); } } /** * Restores a session after an activity was temporarily destroyed. * * @param sessionId The id of the session to restore * @param uid UID of the process that tries to restore the session * @param activityToken The new instance of the activity * @param appCallback The callbacks to the activity */ boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, @NonNull IBinder appCallback) { final Session session = mSessions.get(sessionId); if (session == null || uid != session.uid) { return false; } else { session.switchActivity(activityToken, appCallback); return true; } } /** * Updates a session and returns whether it should be restarted. */ @GuardedBy("mLock") boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, AutofillValue value, int action, int flags) { final Session session = mSessions.get(sessionId); if (session == null || session.uid != uid) { if ((flags & FLAG_MANUAL_REQUEST) != 0) { if (sDebug) { Slog.d(TAG, "restarting session " + sessionId + " due to manual request on " + autofillId); } return true; } if (sVerbose) { Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId + "(" + uid + ")"); } return false; } session.updateLocked(autofillId, virtualBounds, value, action, flags); return false; } @GuardedBy("mLock") void removeSessionLocked(int sessionId) { mSessions.remove(sessionId); } /** * Ges the previous sessions asked to be kept alive in a given activity task. * * @param session session calling this method (so it's excluded from the result). */ @Nullable @GuardedBy("mLock") ArrayList getPreviousSessionsLocked(@NonNull Session session) { final int size = mSessions.size(); ArrayList previousSessions = null; for (int i = 0; i < size; i++) { final Session previousSession = mSessions.valueAt(i); if (previousSession.taskId == session.taskId && previousSession.id != session.id && (previousSession.getSaveInfoFlagsLocked() & SaveInfo.FLAG_DELAY_SAVE) != 0) { if (previousSessions == null) { previousSessions = new ArrayList<>(size); } previousSessions.add(previousSession); } } // TODO(b/113281366): remove returned sessions / add CTS test return previousSessions; } void handleSessionSave(Session session) { synchronized (mLock) { if (mSessions.get(session.id) == null) { Slog.w(TAG, "handleSessionSave(): already gone: " + session.id); return; } session.callSaveLocked(); } } void onPendingSaveUi(int operation, @NonNull IBinder token) { if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token); synchronized (mLock) { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { final Session session = mSessions.valueAt(i); if (session.isSaveUiPendingForTokenLocked(token)) { session.onPendingSaveUi(operation, token); return; } } } if (sDebug) { Slog.d(TAG, "No pending Save UI for token " + token + " and operation " + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_", operation)); } } @GuardedBy("mLock") @Override // from PerUserSystemService protected void handlePackageUpdateLocked(@NonNull String packageName) { final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); if (serviceInfo != null && serviceInfo.packageName.equals(packageName)) { resetExtServiceLocked(); } } @GuardedBy("mLock") void resetExtServiceLocked() { if (sVerbose) Slog.v(TAG, "reset autofill service."); mFieldClassificationStrategy.reset(); } @GuardedBy("mLock") void destroyLocked() { if (sVerbose) Slog.v(TAG, "destroyLocked()"); resetExtServiceLocked(); final int numSessions = mSessions.size(); final ArraySet remoteFillServices = new ArraySet<>(numSessions); for (int i = 0; i < numSessions; i++) { final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked(); if (remoteFillService != null) { remoteFillServices.add(remoteFillService); } } mSessions.clear(); for (int i = 0; i < remoteFillServices.size(); i++) { remoteFillServices.valueAt(i).destroy(); } sendStateToClients(/* resetclient=*/ true); if (mClients != null) { mClients.kill(); mClients = null; } } /** * Initializes the last fill selection after an autofill service returned a new * {@link FillResponse}. */ void setLastResponse(int sessionId, @NonNull FillResponse response) { synchronized (mLock) { mEventHistory = new FillEventHistory(sessionId, response.getClientState()); } } /** * Resets the last fill selection. */ void resetLastResponse() { synchronized (mLock) { mEventHistory = null; } } @GuardedBy("mLock") private boolean isValidEventLocked(String method, int sessionId) { if (mEventHistory == null) { Slog.w(TAG, method + ": not logging event because history is null"); return false; } if (sessionId != mEventHistory.getSessionId()) { if (sDebug) { Slog.d(TAG, method + ": not logging event for session " + sessionId + " because tracked session is " + mEventHistory.getSessionId()); } return false; } return true; } /** * Updates the last fill selection when an authentication was selected. */ void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null, null, null, null, null, null, null)); } } } /** * Updates the last fill selection when an dataset authentication was selected. */ void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, clientState, null, null, null, null, null, null, null, null)); } } } /** * Updates the last fill selection when an save Ui is shown. */ void logSaveShown(int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("logSaveShown()", sessionId)) { mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null, null, null, null, null, null, null, null)); } } } /** * Updates the last fill response when a dataset was selected. */ void logDatasetSelected(@Nullable String selectedDataset, int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("logDatasetSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, null, null, null, null, null, null, null)); } } } /** * Updates the last fill response when an autofill context is committed. */ @GuardedBy("mLock") void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, @Nullable ArrayList selectedDatasets, @Nullable ArraySet ignoredDatasets, @Nullable ArrayList changedFieldIds, @Nullable ArrayList changedDatasetIds, @Nullable ArrayList manuallyFilledFieldIds, @Nullable ArrayList> manuallyFilledDatasetIds, @NonNull ComponentName appComponentName, boolean compatMode) { logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, null, null, appComponentName, compatMode); } @GuardedBy("mLock") void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, @Nullable ArrayList selectedDatasets, @Nullable ArraySet ignoredDatasets, @Nullable ArrayList changedFieldIds, @Nullable ArrayList changedDatasetIds, @Nullable ArrayList manuallyFilledFieldIds, @Nullable ArrayList> manuallyFilledDatasetIds, @Nullable ArrayList detectedFieldIdsList, @Nullable ArrayList detectedFieldClassificationsList, @NonNull ComponentName appComponentName, boolean compatMode) { if (isValidEventLocked("logDatasetNotSelected()", sessionId)) { if (sVerbose) { Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId + ", selectedDatasets=" + selectedDatasets + ", ignoredDatasetIds=" + ignoredDatasets + ", changedAutofillIds=" + changedFieldIds + ", changedDatasetIds=" + changedDatasetIds + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds + ", detectedFieldIds=" + detectedFieldIdsList + ", detectedFieldClassifications=" + detectedFieldClassificationsList + ", appComponentName=" + appComponentName.toShortString() + ", compatMode=" + compatMode); } AutofillId[] detectedFieldsIds = null; FieldClassification[] detectedFieldClassifications = null; if (detectedFieldIdsList != null) { detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; detectedFieldIdsList.toArray(detectedFieldsIds); detectedFieldClassifications = new FieldClassification[detectedFieldClassificationsList.size()]; detectedFieldClassificationsList.toArray(detectedFieldClassifications); final int numberFields = detectedFieldsIds.length; int totalSize = 0; float totalScore = 0; for (int i = 0; i < numberFields; i++) { final FieldClassification fc = detectedFieldClassifications[i]; final List matches = fc.getMatches(); final int size = matches.size(); totalSize += size; for (int j = 0; j < size; j++) { totalScore += matches.get(j).getScore(); } } final int averageScore = (int) ((totalScore * 100) / totalSize); mMetricsLogger.write(Helper .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, appComponentName, getServicePackageName(), sessionId, compatMode) .setCounterValue(numberFields) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); } mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, detectedFieldsIds, detectedFieldClassifications)); } } /** * Gets the fill event history. * * @param callingUid The calling uid * * @return The history or {@code null} if there is none. */ FillEventHistory getFillEventHistory(int callingUid) { synchronized (mLock) { if (mEventHistory != null && isCalledByServiceLocked("getFillEventHistory", callingUid)) { return mEventHistory; } } return null; } // Called by Session - does not need to check uid UserData getUserData() { synchronized (mLock) { return mUserData; } } // Called by AutofillManager UserData getUserData(int callingUid) { synchronized (mLock) { if (isCalledByServiceLocked("getUserData", callingUid)) { return mUserData; } } return null; } // Called by AutofillManager void setUserData(int callingUid, UserData userData) { synchronized (mLock) { if (!isCalledByServiceLocked("setUserData", callingUid)) { return; } mUserData = userData; // Log it final int numberFields = mUserData == null ? 0: mUserData.getCategoryIds().length; // NOTE: contrary to most metrics, the service name is logged as the main package name // here, not as MetricsEvent.FIELD_AUTOFILL_SERVICE mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED) .setPackageName(getServicePackageName()) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, numberFields)); } } @GuardedBy("mLock") private boolean isCalledByServiceLocked(@NonNull String methodName, int callingUid) { final int serviceUid = getServiceUidLocked(); if (serviceUid != callingUid) { Slog.w(TAG, methodName + "() called by UID " + callingUid + ", but service UID is " + serviceUid); return false; } return true; } @GuardedBy("mLock") @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() { return mMaster.getSupportedSmartSuggestionModesLocked(); } @Override @GuardedBy("mLock") protected void dumpLocked(String prefix, PrintWriter pw) { super.dumpLocked(prefix, pw); final String prefix2 = prefix + " "; pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked()); pw.print(prefix); pw.print("Autofill Service Info: "); if (mInfo == null) { pw.println("N/A"); } else { pw.println(); mInfo.dump(prefix2, pw); } pw.print(prefix); pw.print("Default component: "); pw.println(getContext() .getString(R.string.config_defaultAutofillService)); pw.print(prefix); pw.println("mAugmentedAutofillNamer: "); pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); pw.println(); if (mRemoteAugmentedAutofillService != null) { pw.print(prefix); pw.println("RemoteAugmentedAutofillService: "); mRemoteAugmentedAutofillService.dump(prefix2, pw); } if (mRemoteAugmentedAutofillServiceInfo != null) { pw.print(prefix); pw.print("RemoteAugmentedAutofillServiceInfo: "); pw.println(mRemoteAugmentedAutofillServiceInfo); } pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); pw.print(prefix); pw.print("Compat pkgs: "); final ArrayMap compatPkgs = getCompatibilityPackagesLocked(); if (compatPkgs == null) { pw.println("N/A"); } else { pw.println(compatPkgs); } pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); pw.print(prefix); pw.print("Disabled apps: "); if (mDisabledApps == null) { pw.println("N/A"); } else { final int size = mDisabledApps.size(); pw.println(size); final StringBuilder builder = new StringBuilder(); final long now = SystemClock.elapsedRealtime(); for (int i = 0; i < size; i++) { final String packageName = mDisabledApps.keyAt(i); final long expiration = mDisabledApps.valueAt(i); builder.append(prefix).append(prefix) .append(i).append(". ").append(packageName).append(": "); TimeUtils.formatDuration((expiration - now), builder); builder.append('\n'); } pw.println(builder); } pw.print(prefix); pw.print("Disabled activities: "); if (mDisabledActivities == null) { pw.println("N/A"); } else { final int size = mDisabledActivities.size(); pw.println(size); final StringBuilder builder = new StringBuilder(); final long now = SystemClock.elapsedRealtime(); for (int i = 0; i < size; i++) { final ComponentName component = mDisabledActivities.keyAt(i); final long expiration = mDisabledActivities.valueAt(i); builder.append(prefix).append(prefix) .append(i).append(". ").append(component).append(": "); TimeUtils.formatDuration((expiration - now), builder); builder.append('\n'); } pw.println(builder); } final int size = mSessions.size(); if (size == 0) { pw.print(prefix); pw.println("No sessions"); } else { pw.print(prefix); pw.print(size); pw.println(" sessions:"); for (int i = 0; i < size; i++) { pw.print(prefix); pw.print("#"); pw.println(i + 1); mSessions.valueAt(i).dumpLocked(prefix2, pw); } } pw.print(prefix); pw.print("Clients: "); if (mClients == null) { pw.println("N/A"); } else { pw.println(); mClients.dump(pw, prefix2); } if (mEventHistory == null || mEventHistory.getEvents() == null || mEventHistory.getEvents().size() == 0) { pw.print(prefix); pw.println("No event on last fill response"); } else { pw.print(prefix); pw.println("Events of last fill response:"); pw.print(prefix); int numEvents = mEventHistory.getEvents().size(); for (int i = 0; i < numEvents; i++) { final Event event = mEventHistory.getEvents().get(i); pw.println(" " + i + ": eventType=" + event.getType() + " datasetId=" + event.getDatasetId()); } } pw.print(prefix); pw.print("User data: "); if (mUserData == null) { pw.println("N/A"); } else { pw.println(); mUserData.dump(prefix2, pw); } pw.print(prefix); pw.println("Field Classification strategy: "); mFieldClassificationStrategy.dump(prefix2, pw); } @GuardedBy("mLock") void destroySessionsLocked() { if (mSessions.size() == 0) { mUi.destroyAll(null, null, false); return; } while (mSessions.size() > 0) { mSessions.valueAt(0).forceRemoveSelfLocked(); } } @GuardedBy("mLock") void destroySessionsForAugmentedAutofillOnlyLocked() { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { mSessions.valueAt(i).forceRemoveSelfIfForAugmentedAutofillOnlyLocked(); } } // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities @GuardedBy("mLock") void destroyFinishedSessionsLocked() { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { final Session session = mSessions.valueAt(i); if (session.isSavingLocked()) { if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id); session.forceRemoveSelfLocked(); } else { session.destroyAugmentedAutofillWindowsLocked(); } } } @GuardedBy("mLock") void listSessionsLocked(ArrayList output) { final int numSessions = mSessions.size(); if (numSessions <= 0) return; final String fmt = "%d:%s:%s"; for (int i = 0; i < numSessions; i++) { final int id = mSessions.keyAt(i); final String service = mInfo == null ? "no_svc" : mInfo.getServiceInfo().getComponentName().flattenToShortString(); final String augmentedService = mRemoteAugmentedAutofillServiceInfo == null ? "no_aug" : mRemoteAugmentedAutofillServiceInfo.getComponentName().flattenToShortString(); output.add(String.format(fmt, id, service, augmentedService)); } } @GuardedBy("mLock") @Nullable ArrayMap getCompatibilityPackagesLocked() { if (mInfo != null) { return mInfo.getCompatibilityPackages(); } return null; } @GuardedBy("mLock") @Nullable RemoteAugmentedAutofillService getRemoteAugmentedAutofillServiceLocked() { if (mRemoteAugmentedAutofillService == null) { final String serviceName = mMaster.mAugmentedAutofillResolver.getServiceName(mUserId); if (serviceName == null) { if (mMaster.verbose) { Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): not set"); } return null; } final Pair pair = RemoteAugmentedAutofillService .getComponentName(serviceName, mUserId, mMaster.mAugmentedAutofillResolver.isTemporary(mUserId)); if (pair == null) return null; mRemoteAugmentedAutofillServiceInfo = pair.first; final ComponentName componentName = pair.second; if (sVerbose) { Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName); } mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(), componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() { @Override public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { Slog.w(TAG, "remote augmented autofill service died"); final RemoteAugmentedAutofillService remoteService = mRemoteAugmentedAutofillService; if (remoteService != null) { remoteService.destroy(); } mRemoteAugmentedAutofillService = null; } }, mMaster.isInstantServiceAllowed(), mMaster.verbose, mMaster.mAugmentedServiceIdleUnbindTimeoutMs, mMaster.mAugmentedServiceRequestTimeoutMs); } return mRemoteAugmentedAutofillService; } /** * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver} * changed (among other places). */ void updateRemoteAugmentedAutofillService() { synchronized (mLock) { if (mRemoteAugmentedAutofillService != null) { if (sVerbose) { Slog.v(TAG, "updateRemoteAugmentedAutofillService(): " + "destroying old remote service"); } destroySessionsForAugmentedAutofillOnlyLocked(); mRemoteAugmentedAutofillService.destroy(); mRemoteAugmentedAutofillService = null; mRemoteAugmentedAutofillServiceInfo = null; resetAugmentedAutofillWhitelistLocked(); } final boolean available = isAugmentedAutofillServiceAvailableLocked(); if (sVerbose) Slog.v(TAG, "updateRemoteAugmentedAutofillService(): " + available); if (available) { mRemoteAugmentedAutofillService = getRemoteAugmentedAutofillServiceLocked(); } } } private boolean isAugmentedAutofillServiceAvailableLocked() { if (mMaster.verbose) { Slog.v(TAG, "isAugmentedAutofillService(): " + "setupCompleted=" + isSetupCompletedLocked() + ", disabled=" + isDisabledByUserRestrictionsLocked() + ", augmentedService=" + mMaster.mAugmentedAutofillResolver.getServiceName(mUserId)); } if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked() || mMaster.mAugmentedAutofillResolver.getServiceName(mUserId) == null) { return false; } return true; } boolean isAugmentedAutofillServiceForUserLocked(int callingUid) { return mRemoteAugmentedAutofillServiceInfo != null && mRemoteAugmentedAutofillServiceInfo.applicationInfo.uid == callingUid; } /** * Sets which packages and activities can trigger augmented autofill. * * @return whether caller UID is the augmented autofill service for the user */ @GuardedBy("mLock") boolean setAugmentedAutofillWhitelistLocked(@Nullable List packages, @Nullable List activities, int callingUid) { if (!isCalledByAugmentedAutofillServiceLocked("setAugmentedAutofillWhitelistLocked", callingUid)) { return false; } if (mMaster.verbose) { Slog.v(TAG, "setAugmentedAutofillWhitelistLocked(packages=" + packages + ", activities=" + activities + ")"); } whitelistForAugmentedAutofillPackages(packages, activities); final String serviceName; if (mRemoteAugmentedAutofillServiceInfo != null) { serviceName = mRemoteAugmentedAutofillServiceInfo.getComponentName() .flattenToShortString(); } else { Slog.e(TAG, "setAugmentedAutofillWhitelistLocked(): no service"); serviceName = "N/A"; } final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_AUGMENTED_WHITELIST_REQUEST) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, serviceName); if (packages != null) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_PACKAGES, packages.size()); } if (activities != null) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_ACTIVITIES, activities.size()); } mMetricsLogger.write(log); return true; } @GuardedBy("mLock") private boolean isCalledByAugmentedAutofillServiceLocked(@NonNull String methodName, int callingUid) { // Lazy load service first final RemoteAugmentedAutofillService service = getRemoteAugmentedAutofillServiceLocked(); if (service == null) { Slog.w(TAG, methodName + "() called by UID " + callingUid + ", but there is no augmented autofill service defined for user " + getUserId()); return false; } if (getAugmentedAutofillServiceUidLocked() != callingUid) { Slog.w(TAG, methodName + "() called by UID " + callingUid + ", but service UID is " + getAugmentedAutofillServiceUidLocked() + " for user " + getUserId()); return false; } return true; } @GuardedBy("mLock") private int getAugmentedAutofillServiceUidLocked() { if (mRemoteAugmentedAutofillServiceInfo == null) { if (mMaster.verbose) { Slog.v(TAG, "getAugmentedAutofillServiceUid(): " + "no mRemoteAugmentedAutofillServiceInfo"); } return Process.INVALID_UID; } return mRemoteAugmentedAutofillServiceInfo.applicationInfo.uid; } @GuardedBy("mLock") boolean isWhitelistedForAugmentedAutofillLocked(@NonNull ComponentName componentName) { return mMaster.mAugmentedAutofillState.isWhitelisted(mUserId, componentName); } /** * @throws IllegalArgumentException if packages or components are empty. */ private void whitelistForAugmentedAutofillPackages(@Nullable List packages, @Nullable List components) { // TODO(b/123100824): add CTS test for when it's null synchronized (mLock) { if (mMaster.verbose) { Slog.v(TAG, "whitelisting packages: " + packages + "and activities: " + components); } mMaster.mAugmentedAutofillState.setWhitelist(mUserId, packages, components); } } /** * Resets the augmented autofill whitelist. */ @GuardedBy("mLock") void resetAugmentedAutofillWhitelistLocked() { if (mMaster.verbose) { Slog.v(TAG, "resetting augmented autofill whitelist"); } mMaster.mAugmentedAutofillState.resetWhitelist(mUserId); } private void sendStateToClients(boolean resetClient) { final RemoteCallbackList clients; final int userClientCount; synchronized (mLock) { if (mClients == null) { return; } clients = mClients; userClientCount = clients.beginBroadcast(); } try { for (int i = 0; i < userClientCount; i++) { final IAutoFillManagerClient client = clients.getBroadcastItem(i); try { final boolean resetSession; final boolean isEnabled; synchronized (mLock) { resetSession = resetClient || isClientSessionDestroyedLocked(client); isEnabled = isEnabledLocked(); } int flags = 0; if (isEnabled) { flags |= AutofillManager.SET_STATE_FLAG_ENABLED; } if (resetSession) { flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION; } if (resetClient) { flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT; } if (sDebug) { flags |= AutofillManager.SET_STATE_FLAG_DEBUG; } if (sVerbose) { flags |= AutofillManager.SET_STATE_FLAG_VERBOSE; } client.setState(flags); } catch (RemoteException re) { /* ignore */ } } } finally { clients.finishBroadcast(); } } @GuardedBy("mLock") private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) { final int sessionCount = mSessions.size(); for (int i = 0; i < sessionCount; i++) { final Session session = mSessions.valueAt(i); if (session.getClient().equals(client)) { return session.isDestroyed(); } } return true; } /** * Called by {@link Session} when service asked to disable autofill for an app. */ void disableAutofillForApp(@NonNull String packageName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { if (mDisabledApps == null) { mDisabledApps = new ArrayMap<>(1); } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } mDisabledApps.put(packageName, expiration); int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, packageName, getServicePackageName(), sessionId, compatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)); } } /** * Called by {@link Session} when service asked to disable autofill an app. */ void disableAutofillForActivity(@NonNull ComponentName componentName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { if (mDisabledActivities == null) { mDisabledActivities = new ArrayMap<>(1); } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } mDisabledActivities.put(componentName, expiration); final int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; // NOTE: not using Helper.newLogMaker() because we're setting the componentName instead // of package name final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY) .setComponentName(componentName) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName()) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, sessionId); if (compatMode) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); } mMetricsLogger.write(log); } } /** * Checks if autofill is disabled by service to the given activity. */ @GuardedBy("mLock") private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { // Check activities first. long elapsedTime = 0; if (mDisabledActivities != null) { elapsedTime = SystemClock.elapsedRealtime(); final Long expiration = mDisabledActivities.get(componentName); if (expiration != null) { if (expiration >= elapsedTime) return true; // Restriction expired - clean it up. if (sVerbose) { Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list"); } mDisabledActivities.remove(componentName); } } // Then check apps. final String packageName = componentName.getPackageName(); if (mDisabledApps == null) return false; final Long expiration = mDisabledApps.get(packageName); if (expiration == null) return false; if (elapsedTime == 0) { elapsedTime = SystemClock.elapsedRealtime(); } if (expiration >= elapsedTime) return true; // Restriction expired - clean it up. if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); mDisabledApps.remove(packageName); return false; } // Called by AutofillManager, checks UID. boolean isFieldClassificationEnabled(int callingUid) { synchronized (mLock) { if (!isCalledByServiceLocked("isFieldClassificationEnabled", callingUid)) { return false; } return isFieldClassificationEnabledLocked(); } } // Called by internally, no need to check UID. boolean isFieldClassificationEnabledLocked() { return Settings.Secure.getIntForUser( getContext().getContentResolver(), Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 1, mUserId) == 1; } FieldClassificationStrategy getFieldClassificationStrategy() { return mFieldClassificationStrategy; } String[] getAvailableFieldClassificationAlgorithms(int callingUid) { synchronized (mLock) { if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) { return null; } } return mFieldClassificationStrategy.getAvailableAlgorithms(); } String getDefaultFieldClassificationAlgorithm(int callingUid) { synchronized (mLock) { if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) { return null; } } return mFieldClassificationStrategy.getDefaultAlgorithm(); } @Override public String toString() { return "AutofillManagerServiceImpl: [userId=" + mUserId + ", component=" + (mInfo != null ? mInfo.getServiceInfo().getComponentName() : null) + "]"; } /** Task used to prune abandoned session */ private class PruneTask extends AsyncTask { @Override protected Void doInBackground(Void... ignored) { int numSessionsToRemove; SparseArray sessionsToRemove; synchronized (mLock) { numSessionsToRemove = mSessions.size(); sessionsToRemove = new SparseArray<>(numSessionsToRemove); for (int i = 0; i < numSessionsToRemove; i++) { Session session = mSessions.valueAt(i); sessionsToRemove.put(session.id, session.getActivityTokenLocked()); } } final IActivityTaskManager atm = ActivityTaskManager.getService(); // Only remove sessions which's activities are not known to the activity manager anymore for (int i = 0; i < numSessionsToRemove; i++) { try { // The activity manager cannot resolve activities that have been removed if (atm.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) { sessionsToRemove.removeAt(i); i--; numSessionsToRemove--; } } catch (RemoteException e) { Slog.w(TAG, "Cannot figure out if activity is finished", e); } } synchronized (mLock) { for (int i = 0; i < numSessionsToRemove; i++) { Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i)); if (sessionToRemove != null && sessionsToRemove.valueAt(i) == sessionToRemove.getActivityTokenLocked()) { if (sessionToRemove.isSavingLocked()) { if (sVerbose) { Slog.v(TAG, "Session " + sessionToRemove.id + " is saving"); } } else { if (sDebug) { Slog.i(TAG, "Prune session " + sessionToRemove.id + " (" + sessionToRemove.getActivityTokenLocked() + ")"); } sessionToRemove.removeSelfLocked(); } } } } return null; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy