| /* |
| * 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 com.android.server.inputmethod.InputMethodManagerInternal; |
| |
| 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<AutofillManagerServiceImpl, AutofillManagerService> { |
| |
| 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<IAutoFillManagerClient> 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<String, Long> mDisabledApps; |
| |
| /** |
| * Activities disabled by the service; key is component name, value is when they will be enabled |
| * again. |
| */ |
| @GuardedBy("mLock") |
| private ArrayMap<ComponentName, Long> 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. |
| * |
| * <p>They're kept until the {@link AutofillService} finished handling a request, an error |
| * occurs, or the session is abandoned. |
| */ |
| @GuardedBy("mLock") |
| private final SparseArray<Session> 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; |
| |
| private final InputMethodManagerInternal mInputMethodManagerInternal; |
| |
| 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; |
| mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); |
| |
| 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<AutofillId> 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, mInputMethodManagerInternal); |
| 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<Session> getPreviousSessionsLocked(@NonNull Session session) { |
| final int size = mSessions.size(); |
| ArrayList<Session> 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<RemoteFillService> 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 a dataset is shown. |
| */ |
| void logDatasetShown(int sessionId, @Nullable Bundle clientState) { |
| synchronized (mLock) { |
| if (isValidEventLocked("logDatasetShown", sessionId)) { |
| mEventHistory.addEvent( |
| new Event(Event.TYPE_DATASETS_SHOWN, null, 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<String> selectedDatasets, |
| @Nullable ArraySet<String> ignoredDatasets, |
| @Nullable ArrayList<AutofillId> changedFieldIds, |
| @Nullable ArrayList<String> changedDatasetIds, |
| @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, |
| @Nullable ArrayList<ArrayList<String>> 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<String> selectedDatasets, |
| @Nullable ArraySet<String> ignoredDatasets, |
| @Nullable ArrayList<AutofillId> changedFieldIds, |
| @Nullable ArrayList<String> changedDatasetIds, |
| @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, |
| @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, |
| @Nullable ArrayList<AutofillId> detectedFieldIdsList, |
| @Nullable ArrayList<FieldClassification> 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<Match> 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<String, Long> 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<String> 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<String, Long> 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<ServiceInfo, ComponentName> 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.unbind(); |
| } |
| 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.unbind(); |
| 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<String> packages, |
| @Nullable List<ComponentName> 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<String> packages, |
| @Nullable List<ComponentName> 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<IAutoFillManagerClient> 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); |
| } |
| } |
| |
| // Called by AutofillManagerService |
| long getAppDisabledExpirationLocked(@NonNull String packageName) { |
| if (mDisabledApps == null) { |
| return 0; |
| } |
| final Long expiration = mDisabledApps.get(packageName); |
| return expiration != null ? expiration : 0; |
| } |
| |
| // Called by AutofillManagerService |
| @Nullable |
| ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { |
| if (mDisabledActivities != null) { |
| final int size = mDisabledActivities.size(); |
| ArrayMap<String, Long> disabledList = null; |
| for (int i = 0; i < size; i++) { |
| final ComponentName component = mDisabledActivities.keyAt(i); |
| if (packageName.equals(component.getPackageName())) { |
| if (disabledList == null) { |
| disabledList = new ArrayMap<>(); |
| } |
| final long expiration = mDisabledActivities.valueAt(i); |
| disabledList.put(component.flattenToShortString(), expiration); |
| } |
| } |
| return disabledList; |
| } |
| return null; |
| } |
| |
| /** |
| * 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<Void, Void, Void> { |
| @Override |
| protected Void doInBackground(Void... ignored) { |
| int numSessionsToRemove; |
| |
| SparseArray<IBinder> 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; |
| } |
| } |
| } |