2 * Copyright (C) 2017 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server.autofill;
19 import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
20 import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
21 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
22 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
23 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
24 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
25 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
26 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
27 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
29 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
30 import static com.android.server.autofill.Helper.getNumericValue;
31 import static com.android.server.autofill.Helper.sDebug;
32 import static com.android.server.autofill.Helper.sPartitionMaxCount;
33 import static com.android.server.autofill.Helper.sVerbose;
34 import static com.android.server.autofill.Helper.toArray;
35 import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.app.Activity;
40 import android.app.ActivityManager;
41 import android.app.IAssistDataReceiver;
42 import android.app.assist.AssistStructure;
43 import android.app.assist.AssistStructure.AutofillOverlay;
44 import android.app.assist.AssistStructure.ViewNode;
45 import android.content.ComponentName;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.IntentSender;
49 import android.graphics.Bitmap;
50 import android.graphics.Rect;
51 import android.metrics.LogMaker;
52 import android.os.Binder;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.IBinder.DeathRecipient;
57 import android.os.Parcelable;
58 import android.os.RemoteCallback;
59 import android.os.RemoteException;
60 import android.os.SystemClock;
61 import android.service.autofill.AutofillFieldClassificationService.Scores;
62 import android.service.autofill.AutofillService;
63 import android.service.autofill.Dataset;
64 import android.service.autofill.FieldClassification;
65 import android.service.autofill.FieldClassification.Match;
66 import android.service.autofill.FillContext;
67 import android.service.autofill.FillRequest;
68 import android.service.autofill.FillResponse;
69 import android.service.autofill.InternalSanitizer;
70 import android.service.autofill.InternalValidator;
71 import android.service.autofill.SaveInfo;
72 import android.service.autofill.SaveRequest;
73 import android.service.autofill.UserData;
74 import android.service.autofill.ValueFinder;
75 import android.util.ArrayMap;
76 import android.util.ArraySet;
77 import android.util.LocalLog;
78 import android.util.Slog;
79 import android.util.SparseArray;
80 import android.util.TimeUtils;
81 import android.view.KeyEvent;
82 import android.view.autofill.AutofillId;
83 import android.view.autofill.AutofillManager;
84 import android.view.autofill.AutofillValue;
85 import android.view.autofill.IAutoFillManagerClient;
86 import android.view.autofill.IAutofillWindowPresenter;
88 import com.android.internal.R;
89 import com.android.internal.annotations.GuardedBy;
90 import com.android.internal.logging.MetricsLogger;
91 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
92 import com.android.internal.util.ArrayUtils;
93 import com.android.server.autofill.ui.AutoFillUI;
94 import com.android.server.autofill.ui.PendingUi;
96 import java.io.PrintWriter;
97 import java.io.StringWriter;
98 import java.util.ArrayList;
99 import java.util.Arrays;
100 import java.util.Collection;
101 import java.util.Collections;
102 import java.util.List;
103 import java.util.Objects;
104 import java.util.concurrent.atomic.AtomicInteger;
107 * A session for a given activity.
109 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
110 * of the current {@link ViewState} to display the appropriate UI.
112 * <p>Although the autofill requests and callbacks are stateless from the service's point of
113 * view, we need to keep state in the framework side for cases such as authentication. For
114 * example, when service return a {@link FillResponse} that contains all the fields needed
115 * to fill the activity but it requires authentication first, that response need to be held
116 * until the user authenticates or it times out.
118 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
119 AutoFillUI.AutoFillUiCallback, ValueFinder {
120 private static final String TAG = "AutofillSession";
122 private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
124 private final AutofillManagerServiceImpl mService;
125 private final Handler mHandler;
126 private final Object mLock;
127 private final AutoFillUI mUi;
129 private final MetricsLogger mMetricsLogger = new MetricsLogger();
131 private static AtomicInteger sIdCounter = new AtomicInteger();
133 /** Id of the session */
136 /** uid the session is for */
137 public final int uid;
139 /** Flags used to start the session */
140 public final int mFlags;
143 @NonNull private IBinder mActivityToken;
145 /** Component that's being auto-filled */
146 @NonNull private final ComponentName mComponentName;
148 /** Whether the app being autofilled is running in compat mode. */
149 private final boolean mCompatMode;
151 /** Node representing the URL bar on compat mode. */
153 private ViewNode mUrlBar;
156 private boolean mSaveOnAllViewsInvisible;
159 private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
162 * Id of the View currently being displayed.
165 @Nullable private AutofillId mCurrentViewId;
168 private IAutoFillManagerClient mClient;
171 private DeathRecipient mClientVulture;
173 private final RemoteFillService mRemoteFillService;
176 private SparseArray<FillResponse> mResponses;
179 * Contexts read from the app; they will be updated (sanitized, change values for save) before
180 * sent to {@link AutofillService}. Ordered by the time they were read.
183 private ArrayList<FillContext> mContexts;
186 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
188 private boolean mHasCallback;
191 * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
192 * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
195 private Bundle mClientState;
198 private boolean mDestroyed;
200 /** Whether the session is currently saving. */
202 private boolean mIsSaving;
205 * Helper used to handle state of Save UI when it must be hiding to show a custom description
206 * link and later recovered.
209 private PendingUi mPendingSaveUi;
212 * List of dataset ids selected by the user.
215 private ArrayList<String> mSelectedDatasetIds;
218 * When the session started (using elapsed time since boot).
220 private final long mStartTime;
223 * When the UI was shown for the first time (using elapsed time since boot).
226 private long mUiShownTime;
229 private final LocalLog mUiLatencyHistory;
232 private final LocalLog mWtfHistory;
235 * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
238 private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1);
241 * Receiver of assist data from the app's {@link Activity}.
243 private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
245 public void onHandleAssistData(Bundle resultData) throws RemoteException {
246 final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
247 if (structure == null) {
248 Slog.e(TAG, "No assist structure - app might have crashed providing it");
252 final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
253 if (receiverExtras == null) {
254 Slog.e(TAG, "No receiver extras - app might have crashed providing it");
258 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
261 Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
264 final FillRequest request;
265 synchronized (mLock) {
266 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(),
267 // even if if the activity is gone by then, but structure .ensureData() gives a
268 // ONE_WAY warning because system_service could block on app calls. We need to
269 // change AssistStructure so it provides a "one-way" writeToParcel() method that
270 // sends all the data
272 structure.ensureDataForAutofill();
273 } catch (RuntimeException e) {
274 wtf(e, "Exception lazy loading assist structure for %s: %s",
275 structure.getActivityComponent(), e);
279 // Sanitize structure before it's sent to service.
280 final ComponentName componentNameFromApp = structure.getActivityComponent();
281 if (componentNameFromApp == null || !mComponentName.getPackageName()
282 .equals(componentNameFromApp.getPackageName())) {
283 Slog.w(TAG, "Activity " + mComponentName + " forged different component on "
284 + "AssistStructure: " + componentNameFromApp);
285 structure.setActivityComponent(mComponentName);
286 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT)
287 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
288 componentNameFromApp == null ? "null"
289 : componentNameFromApp.flattenToShortString()));
292 // Sanitize URL bar, if needed
293 final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode(
294 mComponentName.getPackageName());
296 Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds));
298 if (urlBarIds != null) {
299 mUrlBar = Helper.sanitizeUrlBar(structure, urlBarIds);
300 if (mUrlBar != null) {
301 final AutofillId urlBarId = mUrlBar.getAutofillId();
303 Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain "
304 + mUrlBar.getWebDomain());
306 final ViewState viewState = new ViewState(Session.this, urlBarId,
307 Session.this, ViewState.STATE_URL_BAR);
308 mViewStates.put(urlBarId, viewState);
312 structure.sanitizeForParceling(true);
314 // Flags used to start the session.
315 final int flags = structure.getFlags();
317 if (mContexts == null) {
318 mContexts = new ArrayList<>(1);
320 mContexts.add(new FillContext(requestId, structure));
322 cancelCurrentRequestLocked();
324 final int numContexts = mContexts.size();
325 for (int i = 0; i < numContexts; i++) {
326 fillContextWithAllowedValuesLocked(mContexts.get(i), flags);
329 // Dispatch a snapshot of the current contexts list since it may change
330 // until the dispatch happens. The items in the list don't need to be cloned
331 // since we don't hold on them anywhere else. The client state is not touched
332 // by us, so no need to copy.
333 request = new FillRequest(requestId, new ArrayList<>(mContexts),
334 mClientState, flags);
337 mRemoteFillService.onFillRequest(request);
341 public void onHandleAssistScreenshot(Bitmap screenshot) {
347 * Returns the ids of all entries in {@link #mViewStates} in the same order.
350 private AutofillId[] getIdsOfAllViewStatesLocked() {
351 final int numViewState = mViewStates.size();
352 final AutofillId[] ids = new AutofillId[numViewState];
353 for (int i = 0; i < numViewState; i++) {
354 ids[i] = mViewStates.valueAt(i).id;
362 public String findByAutofillId(@NonNull AutofillId id) {
363 synchronized (mLock) {
364 AutofillValue value = findValueLocked(id);
366 if (value.isText()) {
367 return value.getTextValue().toString();
370 if (value.isList()) {
371 final CharSequence[] options = getAutofillOptionsFromContextsLocked(id);
372 if (options != null) {
373 final int index = value.getListValue();
374 final CharSequence option = options[index];
375 return option != null ? option.toString() : null;
377 Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id);
386 public AutofillValue findRawValueByAutofillId(AutofillId id) {
387 synchronized (mLock) {
388 return findValueLocked(id);
393 * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
394 * or {@code null} when not found on either of them.
397 private AutofillValue findValueLocked(@NonNull AutofillId id) {
398 final ViewState state = mViewStates.get(id);
400 if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + id);
403 AutofillValue value = state.getCurrentValue();
405 if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + id);
406 value = getValueFromContextsLocked(id);
412 * Updates values of the nodes in the context's structure so that:
414 * - proper node is focused
415 * - autofillValue is sent back to service when it was previously autofilled
416 * - autofillValue is sent in the view used to force a request
418 * @param fillContext The context to be filled
419 * @param flags The flags that started the session
422 private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
423 final ViewNode[] nodes = fillContext
424 .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
426 final int numViewState = mViewStates.size();
427 for (int i = 0; i < numViewState; i++) {
428 final ViewState viewState = mViewStates.valueAt(i);
430 final ViewNode node = nodes[i];
434 "fillContextWithAllowedValuesLocked(): no node for " + viewState.id);
439 final AutofillValue currentValue = viewState.getCurrentValue();
440 final AutofillValue filledValue = viewState.getAutofilledValue();
441 final AutofillOverlay overlay = new AutofillOverlay();
443 // Sanitizes the value if the current value matches what the service sent.
444 if (filledValue != null && filledValue.equals(currentValue)) {
445 overlay.value = currentValue;
448 if (mCurrentViewId != null) {
449 // Updates the focus value.
450 overlay.focused = mCurrentViewId.equals(viewState.id);
451 // Sanitizes the value of the focused field in a manual request.
452 if (overlay.focused && (flags & FLAG_MANUAL_REQUEST) != 0) {
453 overlay.value = currentValue;
456 node.setAutofillOverlay(overlay);
461 * Cancels the last request sent to the {@link #mRemoteFillService}.
464 private void cancelCurrentRequestLocked() {
465 final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
467 // Remove the FillContext as there will never be a response for the service
468 if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
469 final int numContexts = mContexts.size();
471 // It is most likely the last context, hence search backwards
472 for (int i = numContexts - 1; i >= 0; i--) {
473 if (mContexts.get(i).getRequestId() == canceledRequest) {
474 if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
483 * Reads a new structure and then request a new fill response from the fill service.
486 private void requestNewFillResponseLocked(int flags) {
490 requestId = sIdCounter.getAndIncrement();
491 } while (requestId == INVALID_REQUEST_ID);
493 // Create a metrics log for the request
494 final int ordinal = mRequestLogs.size() + 1;
495 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST)
496 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal);
498 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags);
500 mRequestLogs.put(requestId, log);
503 Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId="
504 + requestId + ", flags=" + flags);
507 // If the focus changes very quickly before the first request is returned each focus change
508 // triggers a new partition and we end up with many duplicate partitions. This is
509 // enhanced as the focus change can be much faster than the taking of the assist structure.
510 // Hence remove the currently queued request and replace it with the one queued after the
511 // structure is taken. This causes only one fill request per bust of focus changes.
512 cancelCurrentRequestLocked();
515 final Bundle receiverExtras = new Bundle();
516 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
517 final long identity = Binder.clearCallingIdentity();
519 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver,
520 receiverExtras, mActivityToken, flags)) {
521 Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
524 Binder.restoreCallingIdentity(identity);
526 } catch (RemoteException e) {
527 // Should not happen, it's a local call.
531 Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
532 @NonNull Context context, @NonNull Handler handler, int userId,
533 @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
534 @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
535 @NonNull LocalLog wtfHistory,
536 @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName,
537 boolean compatMode, boolean bindInstantServiceAllowed, int flags) {
541 mStartTime = SystemClock.elapsedRealtime();
546 mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this,
547 bindInstantServiceAllowed);
548 mActivityToken = activityToken;
549 mHasCallback = hasCallback;
550 mUiLatencyHistory = uiLatencyHistory;
551 mWtfHistory = wtfHistory;
552 mComponentName = componentName;
553 mCompatMode = compatMode;
554 setClientLocked(client);
556 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
557 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
561 * Gets the currently registered activity token
563 * @return The activity token
566 @NonNull IBinder getActivityTokenLocked() {
567 return mActivityToken;
571 * Sets new activity and client for this session.
573 * @param newActivity The token of the new activity
574 * @param newClient The client receiving autofill callbacks
576 void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) {
577 synchronized (mLock) {
579 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: "
580 + id + " destroyed");
583 mActivityToken = newActivity;
584 setClientLocked(newClient);
586 // The tracked id are not persisted in the client, hence update them
587 updateTrackedIdsLocked();
592 private void setClientLocked(@NonNull IBinder client) {
593 unlinkClientVultureLocked();
594 mClient = IAutoFillManagerClient.Stub.asInterface(client);
595 mClientVulture = () -> {
596 Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" + mIsSaving);
597 synchronized (mLock) {
599 mUi.hideFillUi(this);
601 mUi.destroyAll(mPendingSaveUi, this, false);
606 mClient.asBinder().linkToDeath(mClientVulture, 0);
607 } catch (RemoteException e) {
608 Slog.w(TAG, "could not set binder death listener on autofill client: " + e);
613 private void unlinkClientVultureLocked() {
614 if (mClient != null && mClientVulture != null) {
615 final boolean unlinked = mClient.asBinder().unlinkToDeath(mClientVulture, 0);
617 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken);
622 // FillServiceCallbacks
624 public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
625 @NonNull String servicePackageName, int requestFlags) {
626 final AutofillId[] fieldClassificationIds;
628 final LogMaker requestLog;
630 synchronized (mLock) {
632 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
633 + id + " destroyed");
637 requestLog = mRequestLogs.get(requestId);
638 if (requestLog != null) {
639 requestLog.setType(MetricsEvent.TYPE_SUCCESS);
641 Slog.w(TAG, "onFillRequestSuccess(): no request log for id " + requestId);
643 if (response == null) {
644 if (requestLog != null) {
645 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
647 processNullResponseLocked(requestFlags);
651 fieldClassificationIds = response.getFieldClassificationIds();
652 if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
653 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
654 processNullResponseLocked(requestFlags);
659 mService.setLastResponse(id, response);
661 int sessionFinishedState = 0;
662 final long disableDuration = response.getDisableDuration();
663 if (disableDuration > 0) {
664 final int flags = response.getFlags();
666 final StringBuilder message = new StringBuilder("Service disabled autofill for ")
667 .append(mComponentName)
668 .append(": flags=").append(flags)
669 .append(", duration=");
670 TimeUtils.formatDuration(disableDuration, message);
671 Slog.d(TAG, message.toString());
673 if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
674 mService.disableAutofillForActivity(mComponentName, disableDuration,
677 mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
680 sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
683 if (((response.getDatasets() == null || response.getDatasets().isEmpty())
684 && response.getAuthentication() == null)
685 || disableDuration > 0) {
686 // Response is "empty" from an UI point of view, need to notify client.
687 notifyUnavailableToClient(sessionFinishedState);
690 if (requestLog != null) {
691 requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
692 response.getDatasets() == null ? 0 : response.getDatasets().size());
693 if (fieldClassificationIds != null) {
694 requestLog.addTaggedData(
695 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS,
696 fieldClassificationIds.length);
700 synchronized (mLock) {
701 processResponseLocked(response, null, requestFlags);
705 // FillServiceCallbacks
707 public void onFillRequestFailure(int requestId, @Nullable CharSequence message,
708 @NonNull String servicePackageName) {
709 onFillRequestFailureOrTimeout(requestId, false, message, servicePackageName);
712 // FillServiceCallbacks
714 public void onFillRequestTimeout(int requestId, @NonNull String servicePackageName) {
715 onFillRequestFailureOrTimeout(requestId, true, null, servicePackageName);
718 private void onFillRequestFailureOrTimeout(int requestId, boolean timedOut,
719 @Nullable CharSequence message, @NonNull String servicePackageName) {
720 synchronized (mLock) {
722 Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
723 + ") rejected - session: " + id + " destroyed");
726 mService.resetLastResponse();
727 final LogMaker requestLog = mRequestLogs.get(requestId);
728 if (requestLog == null) {
729 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
731 requestLog.setType(timedOut ? MetricsEvent.TYPE_CLOSE : MetricsEvent.TYPE_FAILURE);
734 if (message != null) {
735 getUiForShowing().showError(message, this);
740 // FillServiceCallbacks
742 public void onSaveRequestSuccess(@NonNull String servicePackageName,
743 @Nullable IntentSender intentSender) {
744 synchronized (mLock) {
748 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
749 + id + " destroyed");
753 LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
754 .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
755 mMetricsLogger.write(log);
756 if (intentSender != null) {
757 if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
758 startIntentSender(intentSender);
761 // Nothing left to do...
765 // FillServiceCallbacks
767 public void onSaveRequestFailure(@Nullable CharSequence message,
768 @NonNull String servicePackageName) {
769 synchronized (mLock) {
773 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
774 + id + " destroyed");
778 LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
779 .setType(MetricsEvent.TYPE_FAILURE);
780 mMetricsLogger.write(log);
782 getUiForShowing().showError(message, this);
787 * Gets the {@link FillContext} for a request.
789 * @param requestId The id of the request
791 * @return The context or {@code null} if there is no context
794 @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
795 if (mContexts == null) {
799 int numContexts = mContexts.size();
800 for (int i = 0; i < numContexts; i++) {
801 FillContext context = mContexts.get(i);
803 if (context.getRequestId() == requestId) {
811 // FillServiceCallbacks
813 public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
815 Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
816 + "; intentSender=" + intent);
818 final Intent fillInIntent;
819 synchronized (mLock) {
821 Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
822 + id + " destroyed");
825 fillInIntent = createAuthFillInIntentLocked(requestId, extras);
826 if (fillInIntent == null) {
827 forceRemoveSelfLocked();
832 mService.setAuthenticationSelected(id, mClientState);
834 final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
835 mHandler.sendMessage(obtainMessage(
836 Session::startAuthentication,
837 this, authenticationId, intent, fillInIntent));
840 // FillServiceCallbacks
842 public void onServiceDied(RemoteFillService service) {
843 // TODO(b/337565347): implement
846 // AutoFillUiCallback
848 public void fill(int requestId, int datasetIndex, Dataset dataset) {
849 synchronized (mLock) {
851 Slog.w(TAG, "Call to Session#fill() rejected - session: "
852 + id + " destroyed");
856 mHandler.sendMessage(obtainMessage(
858 this, requestId, datasetIndex, dataset, true));
861 // AutoFillUiCallback
864 synchronized (mLock) {
866 Slog.w(TAG, "Call to Session#save() rejected - session: "
867 + id + " destroyed");
871 mHandler.sendMessage(obtainMessage(
872 AutofillManagerServiceImpl::handleSessionSave,
876 // AutoFillUiCallback
878 public void cancelSave() {
879 synchronized (mLock) {
883 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
884 + id + " destroyed");
888 mHandler.sendMessage(obtainMessage(
889 Session::removeSelf, this));
892 // AutoFillUiCallback
894 public void requestShowFillUi(AutofillId id, int width, int height,
895 IAutofillWindowPresenter presenter) {
896 synchronized (mLock) {
898 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: "
899 + id + " destroyed");
902 if (id.equals(mCurrentViewId)) {
904 final ViewState view = mViewStates.get(id);
905 mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
907 } catch (RemoteException e) {
908 Slog.e(TAG, "Error requesting to show fill UI", e);
912 Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view ("
913 + mCurrentViewId + ") anymore");
920 public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) {
921 synchronized (mLock) {
923 Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: "
924 + id + " destroyed");
927 if (id.equals(mCurrentViewId)) {
929 mClient.dispatchUnhandledKey(this.id, id, keyEvent);
930 } catch (RemoteException e) {
931 Slog.e(TAG, "Error requesting to dispatch unhandled key", e);
934 Slog.w(TAG, "Do not dispatch unhandled key on " + id
935 + " as it is not the current view (" + mCurrentViewId + ") anymore");
940 // AutoFillUiCallback
942 public void requestHideFillUi(AutofillId id) {
943 synchronized (mLock) {
944 // NOTE: We allow this call in a destroyed state as the UI is
945 // asked to go away after we get destroyed, so let it do that.
947 mClient.requestHideFillUi(this.id, id);
948 } catch (RemoteException e) {
949 Slog.e(TAG, "Error requesting to hide fill UI", e);
954 // AutoFillUiCallback
956 public void startIntentSender(IntentSender intentSender) {
957 synchronized (mLock) {
959 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: "
960 + id + " destroyed");
965 mHandler.sendMessage(obtainMessage(
966 Session::doStartIntentSender,
967 this, intentSender));
970 private void doStartIntentSender(IntentSender intentSender) {
972 synchronized (mLock) {
973 mClient.startIntentSender(intentSender, null);
975 } catch (RemoteException e) {
976 Slog.e(TAG, "Error launching auth intent", e);
981 void setAuthenticationResultLocked(Bundle data, int authenticationId) {
983 Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
984 + id + " destroyed");
987 if (mResponses == null) {
988 // Typically happens when app explicitly called cancel() while the service was showing
990 Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
994 final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
995 final FillResponse authenticatedResponse = mResponses.get(requestId);
996 if (authenticatedResponse == null || data == null) {
1001 final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(
1003 // Authenticated a dataset - reset view state regardless if we got a response or a dataset
1004 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1005 final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
1006 if (dataset == null) {
1012 final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
1013 final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
1015 Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
1016 + ", clientState=" + newClientState);
1018 if (result instanceof FillResponse) {
1019 logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
1020 replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
1021 } else if (result instanceof Dataset) {
1022 if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
1023 logAuthenticationStatusLocked(requestId,
1024 MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
1025 if (newClientState != null) {
1026 if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
1027 mClientState = newClientState;
1029 final Dataset dataset = (Dataset) result;
1030 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
1031 autoFill(requestId, datasetIdx, dataset, false);
1033 logAuthenticationStatusLocked(requestId,
1034 MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
1037 if (result != null) {
1038 Slog.w(TAG, "service returned invalid auth type: " + result);
1040 logAuthenticationStatusLocked(requestId,
1041 MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
1042 processNullResponseLocked(0);
1047 void setHasCallbackLocked(boolean hasIt) {
1049 Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
1050 + id + " destroyed");
1053 mHasCallback = hasIt;
1058 private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
1059 if (mContexts == null) {
1060 if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
1063 if (mResponses == null) {
1064 // Happens when the activity / session was finished before the service replied, or
1065 // when the service cannot autofill it (and returned a null response).
1066 if (sVerbose && logPrefix != null) {
1067 Slog.v(TAG, logPrefix + ": no responses on session");
1072 final int lastResponseIdx = getLastResponseIndexLocked();
1073 if (lastResponseIdx < 0) {
1074 if (logPrefix != null) {
1075 Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
1076 + ", mViewStates=" + mViewStates);
1081 final FillResponse response = mResponses.valueAt(lastResponseIdx);
1082 if (sVerbose && logPrefix != null) {
1083 Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
1084 + ", mViewStates=" + mViewStates);
1091 private SaveInfo getSaveInfoLocked() {
1092 final FillResponse response = getLastResponseLocked(null);
1093 return response == null ? null : response.getSaveInfo();
1097 * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
1100 public void logContextCommitted() {
1101 mHandler.sendMessage(obtainMessage(
1102 Session::doLogContextCommitted, this));
1105 private void doLogContextCommitted() {
1106 synchronized (mLock) {
1107 logContextCommittedLocked();
1112 private void logContextCommittedLocked() {
1113 final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
1114 if (lastResponse == null) return;
1116 final int flags = lastResponse.getFlags();
1117 if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
1118 if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
1122 ArraySet<String> ignoredDatasets = null;
1123 ArrayList<AutofillId> changedFieldIds = null;
1124 ArrayList<String> changedDatasetIds = null;
1125 ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
1127 boolean hasAtLeastOneDataset = false;
1128 final int responseCount = mResponses.size();
1129 for (int i = 0; i < responseCount; i++) {
1130 final FillResponse response = mResponses.valueAt(i);
1131 final List<Dataset> datasets = response.getDatasets();
1132 if (datasets == null || datasets.isEmpty()) {
1133 if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
1135 for (int j = 0; j < datasets.size(); j++) {
1136 final Dataset dataset = datasets.get(j);
1137 final String datasetId = dataset.getId();
1138 if (datasetId == null) {
1140 Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
1143 hasAtLeastOneDataset = true;
1144 if (mSelectedDatasetIds == null
1145 || !mSelectedDatasetIds.contains(datasetId)) {
1146 if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
1147 if (ignoredDatasets == null) {
1148 ignoredDatasets = new ArraySet<>();
1150 ignoredDatasets.add(datasetId);
1156 final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds();
1158 if (!hasAtLeastOneDataset && fieldClassificationIds == null) {
1160 Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
1161 + "classification ids)");
1166 final UserData userData = mService.getUserData();
1168 for (int i = 0; i < mViewStates.size(); i++) {
1169 final ViewState viewState = mViewStates.valueAt(i);
1170 final int state = viewState.getState();
1172 // When value changed, we need to log if it was:
1173 // - autofilled -> changedDatasetIds
1174 // - not autofilled but matches a dataset value -> manuallyFilledIds
1175 if ((state & ViewState.STATE_CHANGED) != 0) {
1176 // Check if autofilled value was changed
1177 if ((state & ViewState.STATE_AUTOFILLED) != 0) {
1178 final String datasetId = viewState.getDatasetId();
1179 if (datasetId == null) {
1180 // Sanity check - should never happen.
1181 Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
1185 // Must first check if final changed value is not the same as value sent by
1187 final AutofillValue autofilledValue = viewState.getAutofilledValue();
1188 final AutofillValue currentValue = viewState.getCurrentValue();
1189 if (autofilledValue != null && autofilledValue.equals(currentValue)) {
1191 Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
1192 + " because it has same value that was autofilled");
1198 Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
1200 if (changedFieldIds == null) {
1201 changedFieldIds = new ArrayList<>();
1202 changedDatasetIds = new ArrayList<>();
1204 changedFieldIds.add(viewState.id);
1205 changedDatasetIds.add(datasetId);
1207 final AutofillValue currentValue = viewState.getCurrentValue();
1208 if (currentValue == null) {
1210 Slog.d(TAG, "logContextCommitted(): skipping view without current "
1211 + "value ( " + viewState + ")");
1215 // Check if value match a dataset.
1216 if (hasAtLeastOneDataset) {
1217 for (int j = 0; j < responseCount; j++) {
1218 final FillResponse response = mResponses.valueAt(j);
1219 final List<Dataset> datasets = response.getDatasets();
1220 if (datasets == null || datasets.isEmpty()) {
1222 Slog.v(TAG, "logContextCommitted() no datasets at " + j);
1225 for (int k = 0; k < datasets.size(); k++) {
1226 final Dataset dataset = datasets.get(k);
1227 final String datasetId = dataset.getId();
1228 if (datasetId == null) {
1230 Slog.v(TAG, "logContextCommitted() skipping idless "
1231 + "dataset " + dataset);
1234 final ArrayList<AutofillValue> values =
1235 dataset.getFieldValues();
1236 for (int l = 0; l < values.size(); l++) {
1237 final AutofillValue candidate = values.get(l);
1238 if (currentValue.equals(candidate)) {
1240 Slog.d(TAG, "field " + viewState.id + " was "
1241 + "manually filled with value set by "
1242 + "dataset " + datasetId);
1244 if (manuallyFilledIds == null) {
1245 manuallyFilledIds = new ArrayMap<>();
1247 ArraySet<String> datasetIds =
1248 manuallyFilledIds.get(viewState.id);
1249 if (datasetIds == null) {
1250 datasetIds = new ArraySet<>(1);
1251 manuallyFilledIds.put(viewState.id, datasetIds);
1253 datasetIds.add(datasetId);
1256 if (mSelectedDatasetIds == null
1257 || !mSelectedDatasetIds.contains(datasetId)) {
1259 Slog.v(TAG, "adding ignored dataset " + datasetId);
1261 if (ignoredDatasets == null) {
1262 ignoredDatasets = new ArraySet<>();
1264 ignoredDatasets.add(datasetId);
1276 ArrayList<AutofillId> manuallyFilledFieldIds = null;
1277 ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
1279 // Must "flatten" the map to the parcelable collection primitives
1280 if (manuallyFilledIds != null) {
1281 final int size = manuallyFilledIds.size();
1282 manuallyFilledFieldIds = new ArrayList<>(size);
1283 manuallyFilledDatasetIds = new ArrayList<>(size);
1284 for (int i = 0; i < size; i++) {
1285 final AutofillId fieldId = manuallyFilledIds.keyAt(i);
1286 final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
1287 manuallyFilledFieldIds.add(fieldId);
1288 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
1292 // Sets field classification scores
1293 final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
1294 if (userData != null && fcStrategy != null) {
1295 logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
1296 changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
1297 userData, mViewStates.values());
1299 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1300 ignoredDatasets, changedFieldIds, changedDatasetIds,
1301 manuallyFilledFieldIds, manuallyFilledDatasetIds,
1302 mComponentName, mCompatMode);
1307 * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
1308 * {@code fieldId} based on its {@code currentValue} and {@code userData}.
1310 private void logFieldClassificationScoreLocked(
1311 @NonNull FieldClassificationStrategy fcStrategy,
1312 @NonNull ArraySet<String> ignoredDatasets,
1313 @NonNull ArrayList<AutofillId> changedFieldIds,
1314 @NonNull ArrayList<String> changedDatasetIds,
1315 @NonNull ArrayList<AutofillId> manuallyFilledFieldIds,
1316 @NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
1317 @NonNull UserData userData, @NonNull Collection<ViewState> viewStates) {
1319 final String[] userValues = userData.getValues();
1320 final String[] categoryIds = userData.getCategoryIds();
1323 if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
1324 final int valuesLength = userValues == null ? -1 : userValues.length;
1325 final int idsLength = categoryIds == null ? -1 : categoryIds.length;
1326 Slog.w(TAG, "setScores(): user data mismatch: values.length = "
1327 + valuesLength + ", ids.length = " + idsLength);
1331 final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
1333 final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize);
1334 final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
1337 final String algorithm = userData.getFieldClassificationAlgorithm();
1338 final Bundle algorithmArgs = userData.getAlgorithmArgs();
1339 final int viewsSize = viewStates.size();
1341 // First, we get all scores.
1342 final AutofillId[] autofillIds = new AutofillId[viewsSize];
1343 final ArrayList<AutofillValue> currentValues = new ArrayList<>(viewsSize);
1345 for (ViewState viewState : viewStates) {
1346 currentValues.add(viewState.getCurrentValue());
1347 autofillIds[k++] = viewState.id;
1350 // Then use the results, asynchronously
1351 final RemoteCallback callback = new RemoteCallback((result) -> {
1352 if (result == null) {
1353 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
1354 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1355 ignoredDatasets, changedFieldIds, changedDatasetIds,
1356 manuallyFilledFieldIds, manuallyFilledDatasetIds,
1357 mComponentName, mCompatMode);
1360 final Scores scores = result.getParcelable(EXTRA_SCORES);
1361 if (scores == null) {
1362 Slog.w(TAG, "No field classification score on " + result);
1367 // Iteract over all autofill fields first
1368 for (i = 0; i < viewsSize; i++) {
1369 final AutofillId autofillId = autofillIds[i];
1371 // Search the best scores for each category (as some categories could have
1372 // multiple user values
1373 ArrayMap<String, Float> scoresByField = null;
1374 for (j = 0; j < userValues.length; j++) {
1375 final String categoryId = categoryIds[j];
1376 final float score = scores.scores[i][j];
1378 if (scoresByField == null) {
1379 scoresByField = new ArrayMap<>(userValues.length);
1381 final Float currentScore = scoresByField.get(categoryId);
1382 if (currentScore != null && currentScore > score) {
1384 Slog.v(TAG, "skipping score " + score
1385 + " because it's less than " + currentScore);
1390 Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
1393 scoresByField.put(categoryId, score);
1395 else if (sVerbose) {
1396 Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
1399 if (scoresByField == null) {
1400 if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
1404 // Then create the matches for that autofill id
1405 final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
1406 for (j = 0; j < scoresByField.size(); j++) {
1407 final String fieldId = scoresByField.keyAt(j);
1408 final float score = scoresByField.valueAt(j);
1409 matches.add(new Match(fieldId, score));
1411 detectedFieldIds.add(autofillId);
1412 detectedFieldClassifications.add(new FieldClassification(matches));
1414 } catch (ArrayIndexOutOfBoundsException e) {
1415 wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
1419 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
1420 ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
1421 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
1422 mComponentName, mCompatMode);
1425 fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
1429 * Shows the save UI, when session can be saved.
1431 * @return {@code true} if session is done, or {@code false} if it's pending user action.
1434 public boolean showSaveLocked() {
1436 Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
1437 + id + " destroyed");
1440 final FillResponse response = getLastResponseLocked("showSaveLocked()");
1441 final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
1444 * The Save dialog is only shown if all conditions below are met:
1446 * - saveInfo is not null.
1447 * - autofillValue of all required ids is not null.
1448 * - autofillValue of at least one id (required or optional) has changed.
1449 * - there is no Dataset in the last FillResponse whose values of all dataset fields matches
1450 * the current values of all fields in the screen.
1452 if (saveInfo == null) {
1453 if (sVerbose) Slog.v(TAG, "showSaveLocked(): no saveInfo from service");
1457 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
1459 // Cache used to make sure changed fields do not belong to a dataset.
1460 final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
1461 final ArraySet<AutofillId> allIds = new ArraySet<>();
1463 final AutofillId[] requiredIds = saveInfo.getRequiredIds();
1464 boolean allRequiredAreNotEmpty = true;
1465 boolean atLeastOneChanged = false;
1466 if (requiredIds != null) {
1467 for (int i = 0; i < requiredIds.length; i++) {
1468 final AutofillId id = requiredIds[i];
1470 Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds));
1474 final ViewState viewState = mViewStates.get(id);
1475 if (viewState == null) {
1476 Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
1477 allRequiredAreNotEmpty = false;
1481 AutofillValue value = viewState.getCurrentValue();
1482 if (value == null || value.isEmpty()) {
1483 final AutofillValue initialValue = getValueFromContextsLocked(id);
1484 if (initialValue != null) {
1486 Slog.d(TAG, "Value of required field " + id + " didn't change; "
1487 + "using initial value (" + initialValue + ") instead");
1489 value = initialValue;
1492 Slog.d(TAG, "empty value for required " + id );
1494 allRequiredAreNotEmpty = false;
1499 value = getSanitizedValue(sanitizers, id, value);
1500 if (value == null) {
1502 Slog.d(TAG, "value of required field " + id + " failed sanitization");
1504 allRequiredAreNotEmpty = false;
1507 viewState.setSanitizedValue(value);
1508 currentValues.put(id, value);
1509 final AutofillValue filledValue = viewState.getAutofilledValue();
1511 if (!value.equals(filledValue)) {
1512 boolean changed = true;
1513 if (filledValue == null) {
1514 // Dataset was not autofilled, make sure initial value didn't change.
1515 final AutofillValue initialValue = getValueFromContextsLocked(id);
1516 if (initialValue != null && initialValue.equals(value)) {
1518 Slog.d(TAG, "id " + id + " is part of dataset but initial value "
1519 + "didn't change: " + value);
1526 Slog.d(TAG, "found a change on required " + id + ": " + filledValue
1529 atLeastOneChanged = true;
1535 final AutofillId[] optionalIds = saveInfo.getOptionalIds();
1536 if (allRequiredAreNotEmpty) {
1537 if (!atLeastOneChanged && optionalIds != null) {
1538 // No change on required ids yet, look for changes on optional ids.
1539 for (int i = 0; i < optionalIds.length; i++) {
1540 final AutofillId id = optionalIds[i];
1542 final ViewState viewState = mViewStates.get(id);
1543 if (viewState == null) {
1544 Slog.w(TAG, "no ViewState for optional " + id);
1547 if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
1548 final AutofillValue currentValue = viewState.getCurrentValue();
1549 currentValues.put(id, currentValue);
1550 final AutofillValue filledValue = viewState.getAutofilledValue();
1551 if (currentValue != null && !currentValue.equals(filledValue)) {
1553 Slog.d(TAG, "found a change on optional " + id + ": " + filledValue
1554 + " => " + currentValue);
1556 atLeastOneChanged = true;
1560 // Update current values cache based on initial value
1561 final AutofillValue initialValue = getValueFromContextsLocked(id);
1563 Slog.d(TAG, "no current value for " + id + "; initial value is "
1566 if (initialValue != null) {
1567 currentValues.put(id, initialValue);
1572 if (atLeastOneChanged) {
1574 Slog.d(TAG, "at least one field changed, validate fields for save UI");
1576 final InternalValidator validator = saveInfo.getValidator();
1577 if (validator != null) {
1578 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
1581 isValid = validator.isValid(this);
1582 if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
1584 ? MetricsEvent.TYPE_SUCCESS
1585 : MetricsEvent.TYPE_DISMISS);
1586 } catch (Exception e) {
1587 Slog.e(TAG, "Not showing save UI because validation failed:", e);
1588 log.setType(MetricsEvent.TYPE_FAILURE);
1589 mMetricsLogger.write(log);
1593 mMetricsLogger.write(log);
1595 Slog.i(TAG, "not showing save UI because fields failed validation");
1600 // Make sure the service doesn't have the fields already by checking the datasets
1602 final List<Dataset> datasets = response.getDatasets();
1603 if (datasets != null) {
1604 datasets_loop: for (int i = 0; i < datasets.size(); i++) {
1605 final Dataset dataset = datasets.get(i);
1606 final ArrayMap<AutofillId, AutofillValue> datasetValues =
1607 Helper.getFields(dataset);
1609 Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i
1610 + ": " + dataset + "; allIds=" + allIds);
1612 for (int j = 0; j < allIds.size(); j++) {
1613 final AutofillId id = allIds.valueAt(j);
1614 final AutofillValue currentValue = currentValues.get(id);
1615 if (currentValue == null) {
1617 Slog.d(TAG, "dataset has value for field that is null: " + id);
1619 continue datasets_loop;
1621 final AutofillValue datasetValue = datasetValues.get(id);
1622 if (!currentValue.equals(datasetValue)) {
1624 Slog.d(TAG, "found a dataset change on id " + id + ": from "
1625 + datasetValue + " to " + currentValue);
1627 continue datasets_loop;
1629 if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id);
1632 Slog.d(TAG, "ignoring Save UI because all fields match contents of "
1633 + "dataset #" + i + ": " + dataset);
1640 Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
1644 // Use handler so logContextCommitted() is logged first
1645 mHandler.sendMessage(obtainMessage(
1646 Session::logSaveShown, this));
1648 final IAutoFillManagerClient client = getClient();
1649 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
1650 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
1651 mService.getServicePackageName(), saveInfo, this,
1652 mComponentName, this, mPendingSaveUi, mCompatMode);
1653 if (client != null) {
1655 client.setSaveUiState(id, true);
1656 } catch (RemoteException e) {
1657 Slog.e(TAG, "Error notifying client to set save UI state to shown: " + e);
1664 // Nothing changed...
1666 Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
1667 + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
1668 + ", atLeastOneChanged=" + atLeastOneChanged);
1673 private void logSaveShown() {
1674 mService.logSaveShown(id, mClientState);
1678 private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
1679 if (saveInfo == null) return null;
1681 final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
1682 if (sanitizerKeys == null) return null;
1684 final int size = sanitizerKeys.length ;
1685 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
1686 if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
1687 final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
1688 for (int i = 0; i < size; i++) {
1689 final InternalSanitizer sanitizer = sanitizerKeys[i];
1690 final AutofillId[] ids = sanitizerValues[i];
1692 Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
1693 + Arrays.toString(ids));
1695 for (AutofillId id : ids) {
1696 sanitizers.put(id, sanitizer);
1703 private AutofillValue getSanitizedValue(
1704 @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
1705 @NonNull AutofillId id,
1706 @NonNull AutofillValue value) {
1707 if (sanitizers == null) return value;
1709 final InternalSanitizer sanitizer = sanitizers.get(id);
1710 if (sanitizer == null) {
1714 final AutofillValue sanitized = sanitizer.sanitize(value);
1715 if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
1720 * Returns whether the session is currently showing the save UI
1723 boolean isSavingLocked() {
1728 * Gets the latest non-empty value for the given id in the autofill contexts.
1732 private AutofillValue getValueFromContextsLocked(AutofillId id) {
1733 final int numContexts = mContexts.size();
1734 for (int i = numContexts - 1; i >= 0; i--) {
1735 final FillContext context = mContexts.get(i);
1736 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id);
1738 final AutofillValue value = node.getAutofillValue();
1740 Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + value);
1742 if (value != null && !value.isEmpty()) {
1751 * Gets the latest autofill options for the given id in the autofill contexts.
1755 private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
1756 final int numContexts = mContexts.size();
1758 for (int i = numContexts - 1; i >= 0; i--) {
1759 final FillContext context = mContexts.get(i);
1760 final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), id);
1761 if (node != null && node.getAutofillOptions() != null) {
1762 return node.getAutofillOptions();
1769 * Calls service when user requested save.
1772 void callSaveLocked() {
1774 Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
1775 + id + " destroyed");
1779 if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
1781 if (mContexts == null) {
1782 Slog.w(TAG, "callSaveLocked(): no contexts");
1786 final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
1787 createSanitizers(getSaveInfoLocked());
1789 final int numContexts = mContexts.size();
1791 for (int contextNum = 0; contextNum < numContexts; contextNum++) {
1792 final FillContext context = mContexts.get(contextNum);
1794 final ViewNode[] nodes =
1795 context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
1797 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
1799 for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
1800 final ViewState viewState = mViewStates.valueAt(viewStateNum);
1802 final AutofillId id = viewState.id;
1803 final AutofillValue value = viewState.getCurrentValue();
1804 if (value == null) {
1805 if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
1808 final ViewNode node = nodes[viewStateNum];
1810 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
1813 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
1815 AutofillValue sanitizedValue = viewState.getSanitizedValue();
1817 if (sanitizedValue == null) {
1818 // Field is optional and haven't been sanitized yet.
1819 sanitizedValue = getSanitizedValue(sanitizers, id, value);
1821 if (sanitizedValue != null) {
1822 node.updateAutofillValue(sanitizedValue);
1823 } else if (sDebug) {
1824 Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
1828 // Sanitize structure before it's sent to service.
1829 context.getStructure().sanitizeForParceling(false);
1832 Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()");
1833 context.getStructure().dump(false);
1837 // Remove pending fill requests as the session is finished.
1838 cancelCurrentRequestLocked();
1840 // Dispatch a snapshot of the current contexts list since it may change
1841 // until the dispatch happens. The items in the list don't need to be cloned
1842 // since we don't hold on them anywhere else. The client state is not touched
1843 // by us, so no need to copy.
1844 final SaveRequest saveRequest = new SaveRequest(new ArrayList<>(mContexts), mClientState,
1845 mSelectedDatasetIds);
1846 mRemoteFillService.onSaveRequest(saveRequest);
1850 * Starts (if necessary) a new fill request upon entering a view.
1852 * <p>A new request will be started in 2 scenarios:
1854 * <li>If the user manually requested autofill.
1855 * <li>If the view is part of a new partition.
1858 * @param id The id of the view that is entered.
1859 * @param viewState The view that is entered.
1860 * @param flags The flag that was passed by the AutofillManager.
1863 private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
1864 @NonNull ViewState viewState, int flags) {
1865 if ((flags & FLAG_MANUAL_REQUEST) != 0) {
1866 if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
1867 viewState.setState(STATE_RESTARTED_SESSION);
1868 requestNewFillResponseLocked(flags);
1872 // If it's not, then check if it it should start a partition.
1873 if (shouldStartNewPartitionLocked(id)) {
1875 Slog.d(TAG, "Starting partition for view id " + id + ": "
1876 + viewState.getStateAsString());
1878 viewState.setState(ViewState.STATE_STARTED_PARTITION);
1879 requestNewFillResponseLocked(flags);
1882 Slog.v(TAG, "Not starting new partition for view " + id + ": "
1883 + viewState.getStateAsString());
1889 * Determines if a new partition should be started for an id.
1891 * @param id The id of the view that is entered
1893 * @return {@code true} iff a new partition should be started
1896 private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
1897 if (mResponses == null) {
1901 final int numResponses = mResponses.size();
1902 if (numResponses >= sPartitionMaxCount) {
1903 Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
1904 + " reached maximum of " + sPartitionMaxCount);
1908 for (int responseNum = 0; responseNum < numResponses; responseNum++) {
1909 final FillResponse response = mResponses.valueAt(responseNum);
1911 if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
1915 final SaveInfo saveInfo = response.getSaveInfo();
1916 if (saveInfo != null) {
1917 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id)
1918 || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) {
1923 final List<Dataset> datasets = response.getDatasets();
1924 if (datasets != null) {
1925 final int numDatasets = datasets.size();
1927 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) {
1928 final ArrayList<AutofillId> fields = datasets.get(dataSetNum).getFieldIds();
1930 if (fields != null && fields.contains(id)) {
1936 if (ArrayUtils.contains(response.getAuthenticationIds(), id)) {
1945 void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
1948 Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
1949 + id + " destroyed");
1953 Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + actionAsString(action)
1954 + ", flags=" + flags);
1956 ViewState viewState = mViewStates.get(id);
1958 if (viewState == null) {
1959 if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
1960 || action == ACTION_VIEW_ENTERED) {
1961 if (sVerbose) Slog.v(TAG, "Creating viewState for " + id);
1962 boolean isIgnored = isIgnoredLocked(id);
1963 viewState = new ViewState(this, id, this,
1964 isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
1965 mViewStates.put(id, viewState);
1967 // TODO(b/73648631): for optimization purposes, should also ignore if change is
1968 // detectable, and batch-send them when the session is finished (but that will
1969 // require tracking detectable fields on AutofillManager)
1971 if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + viewState);
1975 if (sVerbose) Slog.v(TAG, "Ignoring specific action when viewState=null");
1981 case ACTION_START_SESSION:
1982 // View is triggering autofill.
1983 mCurrentViewId = viewState.id;
1984 viewState.update(value, virtualBounds, flags);
1985 viewState.setState(ViewState.STATE_STARTED_SESSION);
1986 requestNewFillResponseLocked(flags);
1988 case ACTION_VALUE_CHANGED:
1989 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
1990 // Must cancel the session if the value of the URL bar changed
1991 final String currentUrl = mUrlBar == null ? null
1992 : mUrlBar.getText().toString().trim();
1993 if (currentUrl == null) {
1994 // Sanity check - shouldn't happen.
1995 wtf(null, "URL bar value changed, but current value is null");
1998 if (value == null || ! value.isText()) {
1999 // Sanity check - shouldn't happen.
2000 wtf(null, "URL bar value changed to null or non-text: %s", value);
2003 final String newUrl = value.getTextValue().toString();
2004 if (newUrl.equals(currentUrl)) {
2005 if (sDebug) Slog.d(TAG, "Ignoring change on URL bar as it's the same");
2008 if (mSaveOnAllViewsInvisible) {
2009 // We cannot cancel the session because it could hinder Save when all views
2010 // are finished, as the URL bar changed callback is usually called before
2011 // the virtual views become invisible.
2013 Slog.d(TAG, "Ignoring change on URL because session will finish when "
2014 + "views are gone");
2018 if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed");
2019 forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
2023 if (!Objects.equals(value, viewState.getCurrentValue())) {
2024 if ((value == null || value.isEmpty())
2025 && viewState.getCurrentValue() != null
2026 && viewState.getCurrentValue().isText()
2027 && viewState.getCurrentValue().getTextValue() != null
2028 && getSaveInfoLocked() != null) {
2029 final int length = viewState.getCurrentValue().getTextValue().length();
2031 Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
2032 + length + " chars long");
2034 final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
2035 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
2036 mMetricsLogger.write(log);
2039 // Always update the internal state.
2040 viewState.setCurrentValue(value);
2042 // Must check if this update was caused by autofilling the view, in which
2043 // case we just update the value, but not the UI.
2044 final AutofillValue filledValue = viewState.getAutofilledValue();
2045 if (filledValue != null && filledValue.equals(value)) {
2047 Slog.v(TAG, "ignoring autofilled change on id " + id);
2051 // Update the internal state...
2052 viewState.setState(ViewState.STATE_CHANGED);
2055 final String filterText;
2056 if (value == null || !value.isText()) {
2059 final CharSequence text = value.getTextValue();
2060 // Text should never be null, but it doesn't hurt to check to avoid a
2062 filterText = (text == null) ? null : text.toString();
2064 getUiForShowing().filterFillUi(filterText, this);
2067 case ACTION_VIEW_ENTERED:
2068 if (sVerbose && virtualBounds != null) {
2069 Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
2072 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
2073 if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
2077 requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
2079 // Remove the UI if the ViewState has changed.
2080 if (mCurrentViewId != viewState.id) {
2081 mUi.hideFillUi(this);
2082 mCurrentViewId = viewState.id;
2085 // If the ViewState is ready to be displayed, onReady() will be called.
2086 viewState.update(value, virtualBounds, flags);
2088 case ACTION_VIEW_EXITED:
2089 if (mCurrentViewId == viewState.id) {
2090 if (sVerbose) Slog.d(TAG, "Exiting view " + id);
2091 mUi.hideFillUi(this);
2092 mCurrentViewId = null;
2096 Slog.w(TAG, "updateLocked(): unknown action: " + action);
2101 * Checks whether a view should be ignored.
2104 private boolean isIgnoredLocked(AutofillId id) {
2105 // Always check the latest response only
2106 final FillResponse response = getLastResponseLocked(null);
2107 if (response == null) return false;
2109 return ArrayUtils.contains(response.getIgnoredIds(), id);
2113 public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
2114 @Nullable AutofillValue value) {
2115 synchronized (mLock) {
2117 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
2118 + id + " destroyed");
2123 String filterText = null;
2124 if (value != null && value.isText()) {
2125 filterText = value.getTextValue().toString();
2128 getUiForShowing().showFillUi(filledId, response, filterText,
2129 mService.getServicePackageName(), mComponentName,
2130 mService.getServiceLabel(), mService.getServiceIcon(), this, id, mCompatMode);
2132 synchronized (mLock) {
2133 if (mUiShownTime == 0) {
2134 // Log first time UI is shown.
2135 mUiShownTime = SystemClock.elapsedRealtime();
2136 final long duration = mUiShownTime - mStartTime;
2138 final StringBuilder msg = new StringBuilder("1st UI for ")
2139 .append(mActivityToken)
2140 .append(" shown in ");
2141 TimeUtils.formatDuration(duration, msg);
2142 Slog.d(TAG, msg.toString());
2144 final StringBuilder historyLog = new StringBuilder("id=").append(id)
2145 .append(" app=").append(mActivityToken)
2146 .append(" svc=").append(mService.getServicePackageName())
2147 .append(" latency=");
2148 TimeUtils.formatDuration(duration, historyLog);
2149 mUiLatencyHistory.log(historyLog.toString());
2151 addTaggedDataToRequestLogLocked(response.getRequestId(),
2152 MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
2157 boolean isDestroyed() {
2158 synchronized (mLock) {
2163 IAutoFillManagerClient getClient() {
2164 synchronized (mLock) {
2169 private void notifyUnavailableToClient(int sessionFinishedState) {
2170 synchronized (mLock) {
2171 if (mCurrentViewId == null) return;
2174 mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
2175 } else if (sessionFinishedState != 0) {
2176 mClient.setSessionFinished(sessionFinishedState);
2178 } catch (RemoteException e) {
2179 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
2185 private void updateTrackedIdsLocked() {
2186 // Only track the views of the last response as only those are reported back to the
2187 // service, see #showSaveLocked
2188 final FillResponse response = getLastResponseLocked(null);
2189 if (response == null) return;
2191 ArraySet<AutofillId> trackedViews = null;
2192 mSaveOnAllViewsInvisible = false;
2193 boolean saveOnFinish = true;
2194 final SaveInfo saveInfo = response.getSaveInfo();
2195 final AutofillId saveTriggerId;
2196 if (saveInfo != null) {
2197 saveTriggerId = saveInfo.getTriggerId();
2198 if (saveTriggerId != null) {
2199 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
2201 mSaveOnAllViewsInvisible =
2202 (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
2204 // We only need to track views if we want to save once they become invisible.
2205 if (mSaveOnAllViewsInvisible) {
2206 if (trackedViews == null) {
2207 trackedViews = new ArraySet<>();
2209 if (saveInfo.getRequiredIds() != null) {
2210 Collections.addAll(trackedViews, saveInfo.getRequiredIds());
2213 if (saveInfo.getOptionalIds() != null) {
2214 Collections.addAll(trackedViews, saveInfo.getOptionalIds());
2217 if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
2218 saveOnFinish = false;
2222 saveTriggerId = null;
2225 // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
2226 // they go away (if they're not savable).
2228 final List<Dataset> datasets = response.getDatasets();
2229 ArraySet<AutofillId> fillableIds = null;
2230 if (datasets != null) {
2231 for (int i = 0; i < datasets.size(); i++) {
2232 final Dataset dataset = datasets.get(i);
2233 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
2234 if (fieldIds == null) continue;
2236 for (int j = 0; j < fieldIds.size(); j++) {
2237 final AutofillId id = fieldIds.get(j);
2238 if (trackedViews == null || !trackedViews.contains(id)) {
2239 fillableIds = ArrayUtils.add(fillableIds, id);
2247 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
2248 + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish);
2250 mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
2251 saveOnFinish, toArray(fillableIds), saveTriggerId);
2252 } catch (RemoteException e) {
2253 Slog.w(TAG, "Cannot set tracked ids", e);
2258 * Sets the state of views that failed to autofill.
2261 void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
2262 for (int i = 0; i < ids.size(); i++) {
2263 final AutofillId id = ids.get(i);
2264 final ViewState viewState = mViewStates.get(id);
2265 if (viewState == null) {
2266 Slog.w(TAG, "setAutofillFailure(): no view for id " + id);
2269 viewState.resetState(ViewState.STATE_AUTOFILLED);
2270 final int state = viewState.getState();
2271 viewState.setState(state | ViewState.STATE_AUTOFILL_FAILED);
2273 Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
2279 private void replaceResponseLocked(@NonNull FillResponse oldResponse,
2280 @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
2281 // Disassociate view states with the old response
2282 setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
2284 newResponse.setRequestId(oldResponse.getRequestId());
2285 // Replace the old response
2286 mResponses.put(newResponse.getRequestId(), newResponse);
2287 // Now process the new response
2288 processResponseLocked(newResponse, newClientState, 0);
2291 private void processNullResponseLocked(int flags) {
2292 if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
2293 if ((flags & FLAG_MANUAL_REQUEST) != 0) {
2294 getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
2296 mService.resetLastResponse();
2297 // Nothing to be done, but need to notify client.
2298 notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
2303 private void processResponseLocked(@NonNull FillResponse newResponse,
2304 @Nullable Bundle newClientState, int flags) {
2305 // Make sure we are hiding the UI which will be shown
2306 // only if handling the current response requires it.
2309 final int requestId = newResponse.getRequestId();
2311 Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
2312 + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
2313 + ",newClientState=" + newClientState);
2316 if (mResponses == null) {
2317 // Set initial capacity as 2 to handle cases where service always requires auth.
2318 // TODO: add a metric for number of responses set by server, so we can use its average
2319 // as the initial array capacitiy.
2320 mResponses = new SparseArray<>(2);
2322 mResponses.put(requestId, newResponse);
2323 mClientState = newClientState != null ? newClientState : newResponse.getClientState();
2325 setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
2326 updateTrackedIdsLocked();
2328 if (mCurrentViewId == null) {
2332 // Updates the UI, if necessary.
2333 final ViewState currentView = mViewStates.get(mCurrentViewId);
2334 currentView.maybeCallOnFillReady(flags);
2338 * Sets the state of all views in the given response.
2341 private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
2342 final List<Dataset> datasets = response.getDatasets();
2343 if (datasets != null) {
2344 for (int i = 0; i < datasets.size(); i++) {
2345 final Dataset dataset = datasets.get(i);
2346 if (dataset == null) {
2347 Slog.w(TAG, "Ignoring null dataset on " + datasets);
2350 setViewStatesLocked(response, dataset, state, clearResponse);
2352 } else if (response.getAuthentication() != null) {
2353 for (AutofillId autofillId : response.getAuthenticationIds()) {
2354 final ViewState viewState = createOrUpdateViewStateLocked(autofillId, state, null);
2355 if (!clearResponse) {
2356 viewState.setResponse(response);
2358 viewState.setResponse(null);
2362 final SaveInfo saveInfo = response.getSaveInfo();
2363 if (saveInfo != null) {
2364 final AutofillId[] requiredIds = saveInfo.getRequiredIds();
2365 if (requiredIds != null) {
2366 for (AutofillId id : requiredIds) {
2367 createOrUpdateViewStateLocked(id, state, null);
2370 final AutofillId[] optionalIds = saveInfo.getOptionalIds();
2371 if (optionalIds != null) {
2372 for (AutofillId id : optionalIds) {
2373 createOrUpdateViewStateLocked(id, state, null);
2378 final AutofillId[] authIds = response.getAuthenticationIds();
2379 if (authIds != null) {
2380 for (AutofillId id : authIds) {
2381 createOrUpdateViewStateLocked(id, state, null);
2387 * Sets the state of all views in the given dataset and response.
2390 private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
2391 int state, boolean clearResponse) {
2392 final ArrayList<AutofillId> ids = dataset.getFieldIds();
2393 final ArrayList<AutofillValue> values = dataset.getFieldValues();
2394 for (int j = 0; j < ids.size(); j++) {
2395 final AutofillId id = ids.get(j);
2396 final AutofillValue value = values.get(j);
2397 final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
2398 final String datasetId = dataset.getId();
2399 if (datasetId != null) {
2400 viewState.setDatasetId(datasetId);
2402 if (response != null) {
2403 viewState.setResponse(response);
2404 } else if (clearResponse) {
2405 viewState.setResponse(null);
2411 private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
2412 @Nullable AutofillValue value) {
2413 ViewState viewState = mViewStates.get(id);
2414 if (viewState != null) {
2415 viewState.setState(state);
2417 viewState = new ViewState(this, id, this, state);
2419 Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state);
2421 mViewStates.put(id, viewState);
2423 if ((state & ViewState.STATE_AUTOFILLED) != 0) {
2424 viewState.setAutofilledValue(value);
2429 void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
2431 Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
2432 + "; dataset=" + dataset);
2434 synchronized (mLock) {
2436 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
2437 + id + " destroyed");
2440 // Autofill it directly...
2441 if (dataset.getAuthentication() == null) {
2442 if (generateEvent) {
2443 mService.logDatasetSelected(dataset.getId(), id, mClientState);
2446 autoFillApp(dataset);
2450 // ...or handle authentication.
2451 mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
2452 setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
2453 final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
2454 if (fillInIntent == null) {
2455 forceRemoveSelfLocked();
2458 final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
2460 startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
2465 CharSequence getServiceName() {
2466 synchronized (mLock) {
2467 return mService.getServiceName();
2471 // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
2474 private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
2475 final Intent fillInIntent = new Intent();
2477 final FillContext context = getFillContextByRequestIdLocked(requestId);
2479 if (context == null) {
2480 wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
2481 requestId, mContexts);
2484 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
2485 fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
2486 return fillInIntent;
2489 private void startAuthentication(int authenticationId, IntentSender intent,
2490 Intent fillInIntent) {
2492 synchronized (mLock) {
2493 mClient.authenticate(id, authenticationId, intent, fillInIntent);
2495 } catch (RemoteException e) {
2496 Slog.e(TAG, "Error launching auth intent", e);
2501 public String toString() {
2502 return "Session: [id=" + id + ", component=" + mComponentName + "]";
2506 void dumpLocked(String prefix, PrintWriter pw) {
2507 final String prefix2 = prefix + " ";
2508 pw.print(prefix); pw.print("id: "); pw.println(id);
2509 pw.print(prefix); pw.print("uid: "); pw.println(uid);
2510 pw.print(prefix); pw.print("flags: "); pw.println(mFlags);
2511 pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
2512 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
2513 pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
2514 pw.print(prefix); pw.print("Time to show UI: ");
2515 if (mUiShownTime == 0) {
2518 TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
2521 final int requestLogsSizes = mRequestLogs.size();
2522 pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes);
2523 for (int i = 0; i < requestLogsSizes; i++) {
2524 final int requestId = mRequestLogs.keyAt(i);
2525 final LogMaker log = mRequestLogs.valueAt(i);
2526 pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req=");
2527 pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println();
2529 pw.print(prefix); pw.print("mResponses: ");
2530 if (mResponses == null) {
2533 pw.println(mResponses.size());
2534 for (int i = 0; i < mResponses.size(); i++) {
2535 pw.print(prefix2); pw.print('#'); pw.print(i);
2536 pw.print(' '); pw.println(mResponses.valueAt(i));
2539 pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
2540 pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
2541 pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
2542 pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi);
2543 final int numberViews = mViewStates.size();
2544 pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
2545 for (int i = 0; i < numberViews; i++) {
2546 pw.print(prefix); pw.print("ViewState at #"); pw.println(i);
2547 mViewStates.valueAt(i).dump(prefix2, pw);
2550 pw.print(prefix); pw.print("mContexts: " );
2551 if (mContexts != null) {
2552 int numContexts = mContexts.size();
2553 for (int i = 0; i < numContexts; i++) {
2554 FillContext context = mContexts.get(i);
2556 pw.print(prefix2); pw.print(context);
2558 pw.println("AssistStructure dumped at logcat)");
2560 // TODO: add method on AssistStructure to dump on pw
2561 context.getStructure().dump(false);
2568 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
2569 if (mClientState != null) {
2570 pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw
2573 pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode);
2574 pw.print(prefix); pw.print("mUrlBar: ");
2575 if (mUrlBar == null) {
2578 pw.print("id="); pw.print(mUrlBar.getAutofillId());
2579 pw.print(" domain="); pw.print(mUrlBar.getWebDomain());
2580 pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText());
2582 pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println(
2583 mSaveOnAllViewsInvisible);
2584 pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds);
2585 mRemoteFillService.dump(prefix, pw);
2588 private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) {
2589 pw.print("CAT="); pw.print(log.getCategory());
2590 pw.print(", TYPE=");
2591 final int type = log.getType();
2593 case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break;
2594 case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break;
2595 case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break;
2596 default: pw.print("UNSUPPORTED");
2598 pw.print('('); pw.print(type); pw.print(')');
2599 pw.print(", PKG="); pw.print(log.getPackageName());
2600 pw.print(", SERVICE="); pw.print(log
2601 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE));
2602 pw.print(", ORDINAL="); pw.print(log
2603 .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL));
2604 dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS);
2605 dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS);
2606 dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION);
2607 final int authStatus =
2608 getNumericValue(log, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS);
2609 if (authStatus != 0) {
2610 pw.print(", AUTH_STATUS=");
2611 switch (authStatus) {
2612 case MetricsEvent.AUTOFILL_AUTHENTICATED:
2613 pw.print("AUTHENTICATED"); break;
2614 case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED:
2615 pw.print("DATASET_AUTHENTICATED"); break;
2616 case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION:
2617 pw.print("INVALID_AUTHENTICATION"); break;
2618 case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION:
2619 pw.print("INVALID_DATASET_AUTHENTICATION"); break;
2620 default: pw.print("UNSUPPORTED");
2622 pw.print('('); pw.print(authStatus); pw.print(')');
2624 dumpNumericValue(pw, log, "FC_IDS",
2625 MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS);
2626 dumpNumericValue(pw, log, "COMPAT_MODE",
2627 MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE);
2630 private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log,
2631 @NonNull String field, int tag) {
2632 final int value = getNumericValue(log, tag);
2634 pw.print(", "); pw.print(field); pw.print('='); pw.print(value);
2638 void autoFillApp(Dataset dataset) {
2639 synchronized (mLock) {
2641 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: "
2642 + id + " destroyed");
2646 // Skip null values as a null values means no change
2647 final int entryCount = dataset.getFieldIds().size();
2648 final List<AutofillId> ids = new ArrayList<>(entryCount);
2649 final List<AutofillValue> values = new ArrayList<>(entryCount);
2650 boolean waitingDatasetAuth = false;
2651 for (int i = 0; i < entryCount; i++) {
2652 if (dataset.getFieldValues().get(i) == null) {
2655 final AutofillId viewId = dataset.getFieldIds().get(i);
2657 values.add(dataset.getFieldValues().get(i));
2658 final ViewState viewState = mViewStates.get(viewId);
2659 if (viewState != null
2660 && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
2662 Slog.v(TAG, "autofillApp(): view " + viewId + " waiting auth");
2664 waitingDatasetAuth = true;
2665 viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
2668 if (!ids.isEmpty()) {
2669 if (waitingDatasetAuth) {
2670 mUi.hideFillUi(this);
2672 if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
2674 mClient.autofill(id, ids, values);
2675 if (dataset.getId() != null) {
2676 if (mSelectedDatasetIds == null) {
2677 mSelectedDatasetIds = new ArrayList<>();
2679 mSelectedDatasetIds.add(dataset.getId());
2681 setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, false);
2683 } catch (RemoteException e) {
2684 Slog.w(TAG, "Error autofilling activity: " + e);
2689 private AutoFillUI getUiForShowing() {
2690 synchronized (mLock) {
2691 mUi.setCallback(this);
2697 * Cleans up this session.
2699 * <p>Typically called in 2 scenarios:
2702 * <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}.
2703 * <li>When the service hosting the session is finished (for example, because the user
2708 RemoteFillService destroyLocked() {
2712 unlinkClientVultureLocked();
2713 mUi.destroyAll(mPendingSaveUi, this, true);
2714 mUi.clearCallback(this);
2718 final int totalRequests = mRequestLogs.size();
2719 if (totalRequests > 0) {
2720 if (sVerbose) Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " requests");
2721 for (int i = 0; i < totalRequests; i++) {
2722 final LogMaker log = mRequestLogs.valueAt(i);
2723 mMetricsLogger.write(log);
2726 mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
2727 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
2729 return mRemoteFillService;
2733 * Cleans up this session and remove it from the service always, even if it does have a pending
2737 void forceRemoveSelfLocked() {
2738 forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN);
2742 void forceRemoveSelfLocked(int clientState) {
2743 if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
2745 final boolean isPendingSaveUi = isSaveUiPendingLocked();
2746 mPendingSaveUi = null;
2748 mUi.destroyAll(mPendingSaveUi, this, false);
2749 if (!isPendingSaveUi) {
2751 mClient.setSessionFinished(clientState);
2752 } catch (RemoteException e) {
2753 Slog.e(TAG, "Error notifying client to finish session", e);
2759 * Thread-safe version of {@link #removeSelfLocked()}.
2761 private void removeSelf() {
2762 synchronized (mLock) {
2768 * Cleans up this session and remove it from the service, but but only if it does not have a
2772 void removeSelfLocked() {
2773 if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
2775 Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: "
2776 + id + " destroyed");
2779 if (isSaveUiPendingLocked()) {
2780 Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
2784 final RemoteFillService remoteFillService = destroyLocked();
2785 mService.removeSessionLocked(id);
2786 if (remoteFillService != null) {
2787 remoteFillService.destroy();
2791 void onPendingSaveUi(int operation, @NonNull IBinder token) {
2792 getUiForShowing().onPendingSaveUi(operation, token);
2796 * Checks whether this session is hiding the Save UI to handle a custom description link for
2797 * a specific {@code token} created by
2798 * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
2801 boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
2802 return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
2806 * Checks whether this session is hiding the Save UI to handle a custom description link.
2809 private boolean isSaveUiPendingLocked() {
2810 return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
2814 private int getLastResponseIndexLocked() {
2815 // The response ids are monotonically increasing so
2816 // we just find the largest id which is the last. We
2817 // do not rely on the internal ordering in sparse
2818 // array to avoid - wow this stopped working!?
2819 int lastResponseIdx = -1;
2820 int lastResponseId = -1;
2821 if (mResponses != null) {
2822 final int responseCount = mResponses.size();
2823 for (int i = 0; i < responseCount; i++) {
2824 if (mResponses.keyAt(i) > lastResponseId) {
2825 lastResponseIdx = i;
2829 return lastResponseIdx;
2832 private LogMaker newLogMaker(int category) {
2833 return newLogMaker(category, mService.getServicePackageName());
2836 private LogMaker newLogMaker(int category, String servicePackageName) {
2837 return Helper.newLogMaker(category, mComponentName, servicePackageName, id, mCompatMode);
2840 private void writeLog(int category) {
2841 mMetricsLogger.write(newLogMaker(category));
2844 private void logAuthenticationStatusLocked(int requestId, int status) {
2845 addTaggedDataToRequestLogLocked(requestId,
2846 MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status);
2849 private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) {
2850 final LogMaker requestLog = mRequestLogs.get(requestId);
2851 if (requestLog == null) {
2853 "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId);
2856 requestLog.addTaggedData(tag, value);
2859 private static String requestLogToString(@NonNull LogMaker log) {
2860 final StringWriter sw = new StringWriter();
2861 final PrintWriter pw = new PrintWriter(sw);
2862 dumpRequestLog(pw, log);
2864 return sw.toString();
2867 private void wtf(@Nullable Exception e, String fmt, Object...args) {
2868 final String message = String.format(fmt, args);
2869 mWtfHistory.log(message);
2872 Slog.wtf(TAG, message, e);
2874 Slog.wtf(TAG, message);
2878 private static String actionAsString(int action) {
2880 case ACTION_START_SESSION:
2881 return "START_SESSION";
2882 case ACTION_VIEW_ENTERED:
2883 return "VIEW_ENTERED";
2884 case ACTION_VIEW_EXITED:
2885 return "VIEW_EXITED";
2886 case ACTION_VALUE_CHANGED:
2887 return "VALUE_CHANGED";
2889 return "UNKNOWN_" + action;