Merge "[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" into udc-dev

This commit is contained in:
Simranjit Kohli
2023-03-08 22:42:36 +00:00
committed by Android (Google) Code Review
2 changed files with 161 additions and 63 deletions

View File

@@ -28,6 +28,7 @@ import android.content.ClipData;
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 @@ public final class Dataset implements Parcelable {
* 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;
@@ -451,6 +452,15 @@ public final class Dataset implements Parcelable {
private boolean mDestroyed;
@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.
*
@@ -1051,29 +1061,40 @@ public final class Dataset implements Parcelable {
*/
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 @@ public final class Dataset implements Parcelable {
* 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 final class Dataset implements Parcelable {
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 @@ public final class Dataset implements Parcelable {
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 @@ public final class Dataset implements Parcelable {
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) {

View File

@@ -742,6 +742,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags) {
final AutofillId[] fieldClassificationIds;
final LogMaker requestLog;
@@ -1609,10 +1619,68 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
mClassificationState.mHintsToAutofillIdMap;
// TODO(266379948): Handle group hints too.
ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
mClassificationState.mGroupHintsToAutofillIdMap;
@@ -1649,7 +1718,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mClassificationGroupHintsMap = new ArrayMap<>();
mHintsToAutofillIdMap = new ArrayMap<>();
mGroupHintsToAutofillIdMap = new ArrayMap<>();
mClassificationCombinedHintsMap = new ArrayMap<>();
Set<android.service.assist.classification.FieldClassification> classifications =
response.getClassifications();