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:
committed by
Android (Google) Code Review
commit
041ef18b1e
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user