diff options
| -rw-r--r-- | core/java/android/service/autofill/Dataset.java | 136 | ||||
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/Session.java | 88 |
2 files changed, 161 insertions, 63 deletions
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 0ef8bb64acaf..e81ca1ada98a 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -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; @@ -452,6 +453,15 @@ public final class Dataset implements Parcelable { @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 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) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b54dbbf14ce2..7f6ad431c601 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 @@ 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(); |