diff options
4 files changed, 85 insertions, 9 deletions
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index a2ec0993c2c9..65b0efcbe032 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.widget.RemoteViews; + import com.android.internal.util.Preconditions; import java.util.ArrayList; diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 389341b0836e..f8a94d6ce467 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -118,6 +118,9 @@ import java.util.Arrays; * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value * presented in the view). + * <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the + * screen state (i.e., all required and optional fields in the dataset have the same value as + * the fields in the screen). * <li>The user explicitly tapped the UI affordance asking to save data for autofill. * </ul> * diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 86e32e041a96..086dd29f0c97 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -16,11 +16,16 @@ package com.android.server.autofill; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; +import android.service.autofill.Dataset; +import android.util.ArrayMap; import android.util.ArraySet; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import java.util.Set; @@ -82,4 +87,16 @@ public final class Helper { } return array; } + + @NonNull + static ArrayMap<AutofillId, AutofillValue> getFields(@NonNull Dataset dataset) { + final ArrayList<AutofillId> ids = dataset.getFieldIds(); + final ArrayList<AutofillValue> values = dataset.getFieldValues(); + final int size = ids == null ? 0 : ids.size(); + final ArrayMap<AutofillId, AutofillValue> fields = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + fields.put(ids.get(i), values.get(i)); + } + return fields; + } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index af47f0c29ef4..41077568474e 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -804,15 +804,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /* * The Save dialog is only shown if all conditions below are met: * - * - saveInfo is not null - * - autofillValue of all required ids is not null + * - saveInfo is not null. + * - autofillValue of all required ids is not null. * - autofillValue of at least one id (required or optional) has changed. + * - there is no Dataset in the last FillResponse whose values of all dataset fields matches + * the current values of all fields in the screen. */ - if (saveInfo == null) { return true; } + // Cache used to make sure changed fields do not belong to a dataset. + final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>(); + final ArraySet<AutofillId> allIds = new ArraySet<>(); + final AutofillId[] requiredIds = saveInfo.getRequiredIds(); boolean allRequiredAreNotEmpty = true; boolean atLeastOneChanged = false; @@ -823,6 +828,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds)); continue; } + allIds.add(id); final ViewState viewState = mViewStates.get(id); if (viewState == null) { Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id); @@ -841,18 +847,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState value = initialValue; } else { if (sDebug) { - Slog.d(TAG, "showSaveLocked(): empty value for required " + id ); + Slog.d(TAG, "empty value for required " + id ); } allRequiredAreNotEmpty = false; break; } } + currentValues.put(id, value); final AutofillValue filledValue = viewState.getAutofilledValue(); if (!value.equals(filledValue)) { if (sDebug) { - Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": " - + filledValue + " => " + value); + Slog.d(TAG, "found a change on required " + id + ": " + filledValue + + " => " + value); } atLeastOneChanged = true; } @@ -865,22 +872,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // No change on required ids yet, look for changes on optional ids. for (int i = 0; i < optionalIds.length; i++) { final AutofillId id = optionalIds[i]; + allIds.add(id); final ViewState viewState = mViewStates.get(id); if (viewState == null) { - Slog.w(TAG, "showSaveLocked(): no ViewState for optional " + id); + Slog.w(TAG, "no ViewState for optional " + id); continue; } if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { final AutofillValue currentValue = viewState.getCurrentValue(); + currentValues.put(id, currentValue); final AutofillValue filledValue = viewState.getAutofilledValue(); if (currentValue != null && !currentValue.equals(filledValue)) { if (sDebug) { - Slog.d(TAG, "finishSessionLocked(): found a change on optional " - + id + ": " + filledValue + " => " + currentValue); + Slog.d(TAG, "found a change on optional " + id + ": " + filledValue + + " => " + currentValue); } atLeastOneChanged = true; break; } + } else { + // Update current values cache based on initial value + final AutofillValue initialValue = getValueFromContexts(id); + if (sDebug) { + Slog.d(TAG, "no current value for " + id + "; initial value is " + + initialValue); + } + if (initialValue != null) { + currentValues.put(id, initialValue); + } } } } @@ -907,6 +926,42 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + // Make sure the service doesn't have the fields already by checking the datasets + // content. + final ArrayList<Dataset> datasets = response.getDatasets(); + if (datasets != null) { + datasets_loop: for (int i = 0; i < datasets.size(); i++) { + final Dataset dataset = datasets.get(i); + final ArrayMap<AutofillId, AutofillValue> datasetValues = + Helper.getFields(dataset); + if (sVerbose) { + Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i + + ": " + dataset + "; allIds=" + allIds); + } + for (int j = 0; j < allIds.size(); j++) { + final AutofillId id = allIds.valueAt(j); + final AutofillValue currentValue = currentValues.get(id); + if (currentValue == null) { + if (sDebug) { + Slog.d(TAG, "dataset has value for field that is null: " + id); + } + continue datasets_loop; + } + final AutofillValue datasetValue = datasetValues.get(id); + if (!currentValue.equals(datasetValue)) { + if (sDebug) Slog.d(TAG, "found a change on id " + id); + continue datasets_loop; + } + if (sVerbose) Slog.v(TAG, "no changes for id " + id); + } + if (sDebug) { + Slog.d(TAG, "ignoring Save UI because all fields match contents of " + + "dataset #" + i + ": " + dataset); + } + return true; + } + } + if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!"); mService.setSaveShown(id); getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, |