Loading core/java/android/service/autofill/Dataset.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading core/java/android/service/autofill/SaveInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -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> * Loading services/autofill/java/com/android/server/autofill/Helper.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } } services/autofill/java/com/android/server/autofill/Session.java +64 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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; } Loading @@ -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); } } } } Loading @@ -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, Loading Loading
core/java/android/service/autofill/Dataset.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
core/java/android/service/autofill/SaveInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -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> * Loading
services/autofill/java/com/android/server/autofill/Helper.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } }
services/autofill/java/com/android/server/autofill/Session.java +64 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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; } Loading @@ -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); } } } } Loading @@ -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, Loading