[Autofill PCC]: Allow Providers to set both field and type
Allow providers to set both field and type while creating a dataset.
Also, fix NPE, and issue a request if no detection results available.
Test: atest android.autofillservice.cts.unittests.DatasetTest
BUG: 270423491
Change-Id: I2bcdca5e9f406696389ad712e00d066d1b8cefc5
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 0ef8bb64..e81ca1a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -28,6 +28,7 @@
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -434,14 +435,14 @@
* one value for a field or set an authentication intent.
*/
public static final class Builder {
- private ArrayList<AutofillId> mFieldIds;
- private ArrayList<AutofillValue> mFieldValues;
- private ArrayList<RemoteViews> mFieldPresentations;
- private ArrayList<RemoteViews> mFieldDialogPresentations;
- private ArrayList<InlinePresentation> mFieldInlinePresentations;
- private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
- private ArrayList<DatasetFieldFilter> mFieldFilters;
- private ArrayList<String> mAutofillDatatypes;
+ private ArrayList<AutofillId> mFieldIds = new ArrayList<>();
+ private ArrayList<AutofillValue> mFieldValues = new ArrayList();
+ private ArrayList<RemoteViews> mFieldPresentations = new ArrayList();
+ private ArrayList<RemoteViews> mFieldDialogPresentations = new ArrayList();
+ private ArrayList<InlinePresentation> mFieldInlinePresentations = new ArrayList();
+ private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations = new ArrayList();
+ private ArrayList<DatasetFieldFilter> mFieldFilters = new ArrayList();
+ private ArrayList<String> mAutofillDatatypes = new ArrayList();
@Nullable private ClipData mFieldContent;
private RemoteViews mPresentation;
private RemoteViews mDialogPresentation;
@@ -452,6 +453,15 @@
@Nullable private String mId;
/**
+ * Usually, a field will be associated with a single autofill id and/or datatype.
+ * There could be null field value corresponding to different autofill ids or datatye
+ * values, but the implementation is ok with duplicating that information.
+ * This map is just for the purpose of optimization, to reduce the size of the pelled data
+ * over the binder transaction.
+ */
+ private ArrayMap<Field, Integer> mFieldToIndexdMap = new ArrayMap<>();
+
+ /**
* Creates a new builder.
*
* @param presentation The presentation used to visualize this dataset.
@@ -1051,29 +1061,40 @@
*/
public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
throwIfDestroyed();
+
+ if (mFieldToIndexdMap.containsKey(field)) {
+ int index = mFieldToIndexdMap.get(field);
+ if (mFieldIds.get(index) == null) {
+ mFieldIds.set(index, id);
+ return this;
+ }
+ // if the Autofill Id is already set, ignore and proceed as if setting in a new
+ // value.
+ }
+ int index;
if (field == null) {
- setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
+ index = setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
} else {
final DatasetFieldFilter filter = field.getDatasetFieldFilter();
final Presentations presentations = field.getPresentations();
if (presentations == null) {
- setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
+ index = setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
filter, null);
} else {
- setLifeTheUniverseAndEverything(id, field.getValue(),
+ index = setLifeTheUniverseAndEverything(id, field.getValue(),
presentations.getMenuPresentation(),
presentations.getInlinePresentation(),
presentations.getInlineTooltipPresentation(), filter,
presentations.getDialogPresentation());
}
}
+ mFieldToIndexdMap.put(field, index);
return this;
}
/**
- * Adds a field to this Dataset with a specific type and no
- * AutofillId. This is used to send back Field information
- * when Autofilling with platform detections is on.
+ * Adds a field to this Dataset with a specific type. This is used to send back Field
+ * information when Autofilling with platform detections is on.
* Platform detections are on when receiving a populated list from
* FillRequest#getHints().
*
@@ -1086,9 +1107,6 @@
* has two credential pairs, then two Datasets should be created,
* and so on.
*
- * Using this will remove any data populated with
- * setField(@NonNull AutofillId id, @Nullable Field field).
- *
* @param hint An autofill hint returned from {@link
* FillRequest#getHints()}.
*
@@ -1102,19 +1120,29 @@
public @NonNull Dataset.Builder setField(@NonNull String hint, @NonNull Field field) {
throwIfDestroyed();
+ if (mFieldToIndexdMap.containsKey(field)) {
+ int index = mFieldToIndexdMap.get(field);
+ if (mAutofillDatatypes.get(index) == null) {
+ mAutofillDatatypes.set(index, hint);
+ return this;
+ }
+ // if the hint is already set, ignore and proceed as if setting in a new hint.
+ }
+
+ int index;
final DatasetFieldFilter filter = field.getDatasetFieldFilter();
final Presentations presentations = field.getPresentations();
if (presentations == null) {
- setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null,
+ index = setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null,
filter, null);
} else {
- setLifeTheUniverseAndEverything(hint, field.getValue(),
+ index = setLifeTheUniverseAndEverything(hint, field.getValue(),
presentations.getMenuPresentation(),
presentations.getInlinePresentation(),
presentations.getInlineTooltipPresentation(), filter,
presentations.getDialogPresentation());
}
-
+ mFieldToIndexdMap.put(field, index);
return this;
}
@@ -1172,67 +1200,64 @@
return this;
}
- private void setLifeTheUniverseAndEverything(String datatype,
+ /** Returns the index at which this id was modified or inserted */
+ private int setLifeTheUniverseAndEverything(@NonNull String datatype,
@Nullable AutofillValue value,
@Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
@Nullable DatasetFieldFilter filter,
@Nullable RemoteViews dialogPresentation) {
- if (mAutofillDatatypes == null) {
- mFieldValues = new ArrayList<>();
- mFieldPresentations = new ArrayList<>();
- mFieldDialogPresentations = new ArrayList<>();
- mFieldInlinePresentations = new ArrayList<>();
- mFieldInlineTooltipPresentations = new ArrayList<>();
- mFieldFilters = new ArrayList<>();
- mAutofillDatatypes = new ArrayList<>();
- mFieldIds = null;
+ Objects.requireNonNull(datatype, "datatype cannot be null");
+ final int existingIdx = mAutofillDatatypes.indexOf(datatype);
+ if (existingIdx >= 0) {
+ mAutofillDatatypes.add(datatype);
+ mFieldValues.set(existingIdx, value);
+ mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
+ mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+ mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
+ mFieldFilters.set(existingIdx, filter);
+ return existingIdx;
}
+ mFieldIds.add(null);
+ mAutofillDatatypes.add(datatype);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
- mAutofillDatatypes.add(datatype);
+ return mFieldIds.size() - 1;
}
- private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+ /** Returns the index at which this id was modified or inserted */
+ private int setLifeTheUniverseAndEverything(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
@Nullable DatasetFieldFilter filter,
@Nullable RemoteViews dialogPresentation) {
Objects.requireNonNull(id, "id cannot be null");
- if (mFieldIds != null) {
- final int existingIdx = mFieldIds.indexOf(id);
- if (existingIdx >= 0) {
- mFieldValues.set(existingIdx, value);
- mFieldPresentations.set(existingIdx, presentation);
- mFieldDialogPresentations.set(existingIdx, dialogPresentation);
- mFieldInlinePresentations.set(existingIdx, inlinePresentation);
- mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
- mFieldFilters.set(existingIdx, filter);
- return;
- }
- } else {
- mFieldIds = new ArrayList<>();
- mFieldValues = new ArrayList<>();
- mFieldPresentations = new ArrayList<>();
- mFieldDialogPresentations = new ArrayList<>();
- mFieldInlinePresentations = new ArrayList<>();
- mFieldInlineTooltipPresentations = new ArrayList<>();
- mFieldFilters = new ArrayList<>();
- mAutofillDatatypes = null;
+ final int existingIdx = mFieldIds.indexOf(id);
+ if (existingIdx >= 0) {
+ mFieldValues.set(existingIdx, value);
+ mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
+ mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+ mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
+ mFieldFilters.set(existingIdx, filter);
+ return existingIdx;
}
mFieldIds.add(id);
+ mAutofillDatatypes.add(null);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
+ return mFieldIds.size() - 1;
}
/**
@@ -1249,11 +1274,12 @@
throwIfDestroyed();
mDestroyed = true;
if (mFieldIds == null && mAutofillDatatypes == null) {
- throw new IllegalStateException("at least one value must be set");
+ throw new IllegalStateException("at least one of field or datatype must be set");
}
if (mFieldIds != null && mAutofillDatatypes != null) {
- if (mFieldIds.size() > 0 && mAutofillDatatypes.size() > 0) {
- throw new IllegalStateException("both field and datatype were populated");
+ if (mFieldIds.size() == 0 && mAutofillDatatypes.size() == 0) {
+ throw new IllegalStateException(
+ "at least one of field or datatype must be set");
}
}
if (mFieldContent != null) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b54dbbf..7f6ad43 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -742,6 +742,9 @@
return Collections.EMPTY_LIST;
}
final String typeHints = mService.getMaster().getPccProviderHints();
+ if (sVerbose) {
+ Slog.v(TAG, "TypeHints flag:" + typeHints);
+ }
if (TextUtils.isEmpty(typeHints)) {
return new ArrayList<>();
}
@@ -757,7 +760,7 @@
@GuardedBy("mLock")
void maybeRequestFieldClassificationFromServiceLocked() {
if (mClassificationState.mPendingFieldClassificationRequest == null) {
- Log.w(TAG, "Received AssistData without pending classification request");
+ Slog.w(TAG, "Received AssistData without pending classification request");
return;
}
@@ -791,7 +794,8 @@
final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID);
if (sVerbose) {
- Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure);
+ Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": "
+ + structure);
}
synchronized (mLock) {
@@ -1125,6 +1129,13 @@
// structure is taken. This causes only one fill request per burst of focus changes.
cancelCurrentRequestLocked();
+ if (mClassificationState.mHintsToAutofillIdMap == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "triggering field classification");
+ }
+ requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
+ }
+
// Only ask IME to create inline suggestions request when
// 1. Autofill provider supports it or client enabled client suggestions.
// 2. The render service is available.
@@ -1376,7 +1387,6 @@
@Override
public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags) {
-
final AutofillId[] fieldClassificationIds;
final LogMaker requestLog;
@@ -1609,10 +1619,68 @@
Set<Dataset> eligibleDatasets = new ArraySet<>();
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
for (Dataset dataset : response.getDatasets()) {
- if (dataset.getFieldIds() == null) continue;
+ if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
if (dataset.getAutofillDatatypes() != null
- && dataset.getAutofillDatatypes().size() > 0) {
- continue;
+ && !dataset.getAutofillDatatypes().isEmpty()) {
+ // This dataset has information relevant for detection too, so we should filter
+ // them out. It's possible that some fields are applicable to hints only, as such,
+ // they need to be filtered off.
+ // TODO(b/266379948): Verify the logic and add tests
+ // Update dataset to only have non-null fieldValues
+
+ // Figure out if we need to process results.
+ boolean conversionRequired = false;
+ int newSize = dataset.getFieldIds().size();
+ for (AutofillId id : dataset.getFieldIds()) {
+ if (id == null) {
+ conversionRequired = true;
+ newSize--;
+ }
+ }
+
+ if (conversionRequired) {
+ ArrayList<AutofillId> fieldIds = new ArrayList<>(newSize);
+ ArrayList<AutofillValue> fieldValues = new ArrayList<>(newSize);
+ ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(newSize);
+ ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(newSize);
+ ArrayList<InlinePresentation> fieldInlinePresentations =
+ new ArrayList<>(newSize);
+ ArrayList<InlinePresentation> fieldInlineTooltipPresentations =
+ new ArrayList<>(newSize);
+ ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(newSize);
+
+ for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+ AutofillId id = dataset.getFieldIds().get(i);
+ if (id != null) {
+ // Copy over
+ fieldIds.add(id);
+ fieldValues.add(dataset.getFieldValues().get(i));
+ fieldPresentations.add(dataset.getFieldPresentation(i));
+ fieldDialogPresentations.add(dataset.getFieldDialogPresentation(i));
+ fieldInlinePresentations.add(dataset.getFieldInlinePresentation(i));
+ fieldInlineTooltipPresentations.add(
+ dataset.getFieldInlineTooltipPresentation(i));
+ fieldFilters.add(dataset.getFilter(i));
+ }
+ }
+ dataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ }
}
eligibleDatasets.add(dataset);
for (AutofillId id : dataset.getFieldIds()) {
@@ -1639,6 +1707,7 @@
ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
mClassificationState.mHintsToAutofillIdMap;
+ // TODO(266379948): Handle group hints too.
ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
mClassificationState.mGroupHintsToAutofillIdMap;
@@ -1649,7 +1718,8 @@
for (int i = 0; i < datasets.size(); i++) {
Dataset dataset = datasets.get(i);
- if (dataset.getAutofillDatatypes() == null) continue;
+ if (dataset.getAutofillDatatypes() == null
+ || dataset.getAutofillDatatypes().isEmpty()) continue;
if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
ArrayList<AutofillId> fieldIds = new ArrayList<>();
@@ -1661,6 +1731,7 @@
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
+ if (dataset.getAutofillDatatypes().get(0) == null) continue;
String hint = dataset.getAutofillDatatypes().get(j);
if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -4560,7 +4631,7 @@
if (mResponses == null) {
// Set initial capacity as 2 to handle cases where service always requires auth.
// TODO: add a metric for number of responses set by server, so we can use its average
- // as the initial array capacitiy.
+ // as the initial array capacity.
mResponses = new SparseArray<>(2);
}
mResponses.put(requestId, newResponse);
@@ -4982,6 +5053,7 @@
mClassificationGroupHintsMap = new ArrayMap<>();
mHintsToAutofillIdMap = new ArrayMap<>();
mGroupHintsToAutofillIdMap = new ArrayMap<>();
+ mClassificationCombinedHintsMap = new ArrayMap<>();
Set<android.service.assist.classification.FieldClassification> classifications =
response.getClassifications();