diff --git a/core/api/current.txt b/core/api/current.txt index eb1bfc8d42539..fac4cc718f97b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39475,6 +39475,7 @@ package android.service.autofill { method @NonNull public android.service.autofill.Dataset build(); method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender); method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field); + method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull String, @NonNull android.service.autofill.Field); method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String); method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); @@ -39583,6 +39584,7 @@ package android.service.autofill { method @Nullable public android.content.IntentSender getDelayedFillIntentSender(); method @NonNull public java.util.List getFillContexts(); method public int getFlags(); + method @NonNull public java.util.List getHints(); method public int getId(); method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -39611,6 +39613,7 @@ package android.service.autofill { method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.service.autofill.Presentations); method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle); + method @NonNull public android.service.autofill.FillResponse.Builder setDetectedFieldClassifications(@NonNull java.util.Set); method @NonNull public android.service.autofill.FillResponse.Builder setDialogHeader(@NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...); method @NonNull public android.service.autofill.FillResponse.Builder setFillDialogTriggerIds(@NonNull android.view.autofill.AutofillId...); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 85861c3db4666..51e90d28abdc9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2529,6 +2529,7 @@ package android.service.autofill { public final class Dataset implements android.os.Parcelable { method @Nullable public android.content.IntentSender getAuthentication(); + method @Nullable public java.util.ArrayList getAutofillDatatypes(); method @Nullable public android.content.ClipData getFieldContent(); method @Nullable public java.util.ArrayList getFieldIds(); method @Nullable public java.util.ArrayList getFieldValues(); @@ -2549,6 +2550,7 @@ package android.service.autofill { } public final class FillResponse implements android.os.Parcelable { + method @NonNull public java.util.Set getDetectedFieldClassifications(); method public int getFlags(); } @@ -3278,6 +3280,7 @@ package android.view.autofill { field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS = "autofill_credential_manager_ignore_views"; field public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled"; field public static final String DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED = "pcc_classification_enabled"; + field public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS = "pcc_classification_hints"; field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes"; field public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS = "non_autofillable_ime_action_ids"; field public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW = "package_deny_list_for_unimportant_view"; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index e9f099accac13..0fa5e3e3b3237 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -120,6 +120,8 @@ public final class Dataset implements Parcelable { private final ArrayList mFieldInlinePresentations; private final ArrayList mFieldInlineTooltipPresentations; private final ArrayList mFieldFilters; + private final ArrayList mAutofillDatatypes; + @Nullable private final ClipData mFieldContent; private final RemoteViews mPresentation; private final RemoteViews mDialogPresentation; @@ -143,6 +145,14 @@ public final class Dataset implements Parcelable { mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mAuthentication = builder.mAuthentication; mId = builder.mId; + mAutofillDatatypes = builder.mAutofillDatatypes; + } + + /** @hide */ + @TestApi + @SuppressLint({"ConcreteCollection", "NullableCollection"}) + public @Nullable ArrayList getAutofillDatatypes() { + return mAutofillDatatypes; } /** @hide */ @@ -293,6 +303,7 @@ public final class Dataset implements Parcelable { private ArrayList mFieldInlinePresentations; private ArrayList mFieldInlineTooltipPresentations; private ArrayList mFieldFilters; + private ArrayList mAutofillDatatypes; @Nullable private ClipData mFieldContent; private RemoteViews mPresentation; private RemoteViews mDialogPresentation; @@ -921,6 +932,55 @@ public final class Dataset implements Parcelable { 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. + * Platform detections are on when receiving a populated list from + * FillRequest#getHints(). + * + * Populate every field/type known for this user for this app. + * + * For example, if getHints() contains "username" and "password", + * a new Dataset should be created that calls this method twice, + * one for the username, then another for the password (assuming + * the only one credential pair is found for the user). If a user + * 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()}. + * + * @param field the fill information about the field. + * + * @throws IllegalStateException if {@link #build()} was already called + * or this builder also contains AutofillId information + * + * @return this builder. + */ + public @NonNull Dataset.Builder setField( + @NonNull String hint, @NonNull Field field) { + throwIfDestroyed(); + + final DatasetFieldFilter filter = field.getDatasetFieldFilter(); + final Presentations presentations = field.getPresentations(); + if (presentations == null) { + setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null, + filter, null); + } else { + setLifeTheUniverseAndEverything(hint, field.getValue(), + presentations.getMenuPresentation(), + presentations.getInlinePresentation(), + presentations.getInlineTooltipPresentation(), filter, + presentations.getDialogPresentation()); + } + + return this; + } + /** * Sets the value of a field with an explicit filter, and using an * {@link InlinePresentation} to visualize it as an inline suggestion. @@ -958,6 +1018,32 @@ public final class Dataset implements Parcelable { return this; } + private void setLifeTheUniverseAndEverything(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; + } + mFieldValues.add(value); + mFieldPresentations.add(presentation); + mFieldDialogPresentations.add(dialogPresentation); + mFieldInlinePresentations.add(inlinePresentation); + mFieldInlineTooltipPresentations.add(tooltip); + mFieldFilters.add(filter); + mAutofillDatatypes.add(datatype); + } + private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @@ -984,6 +1070,7 @@ public final class Dataset implements Parcelable { mFieldInlinePresentations = new ArrayList<>(); mFieldInlineTooltipPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); + mAutofillDatatypes = null; } mFieldIds.add(id); mFieldValues.add(value); @@ -1007,9 +1094,14 @@ public final class Dataset implements Parcelable { public @NonNull Dataset build() { throwIfDestroyed(); mDestroyed = true; - if (mFieldIds == null) { + if (mFieldIds == null && mAutofillDatatypes == null) { throw new IllegalStateException("at least one value 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 (mFieldContent != null) { if (mFieldIds.size() > 1) { throw new IllegalStateException( @@ -1051,6 +1143,7 @@ public final class Dataset implements Parcelable { parcel.writeTypedList(mFieldInlinePresentations, flags); parcel.writeTypedList(mFieldInlineTooltipPresentations, flags); parcel.writeTypedList(mFieldFilters, flags); + parcel.writeStringList(mAutofillDatatypes); parcel.writeParcelable(mFieldContent, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); @@ -1081,6 +1174,8 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList filters = parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); + final ArrayList datatypes = + parcel.createStringArrayList(); final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class); final IntentSender authentication = parcel.readParcelable(null, @@ -1114,19 +1209,37 @@ public final class Dataset implements Parcelable { builder.setContent(ids.get(0), fieldContent); } final int inlinePresentationsSize = inlinePresentations.size(); - for (int i = 0; i < ids.size(); i++) { - final AutofillId id = ids.get(i); - final AutofillValue value = values.get(i); - final RemoteViews fieldPresentation = presentations.get(i); - final RemoteViews fieldDialogPresentation = dialogPresentations.get(i); - final InlinePresentation fieldInlinePresentation = - i < inlinePresentationsSize ? inlinePresentations.get(i) : null; - final InlinePresentation fieldInlineTooltipPresentation = - i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; - final DatasetFieldFilter filter = filters.get(i); - builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, - fieldInlinePresentation, fieldInlineTooltipPresentation, filter, - fieldDialogPresentation); + + if (ids.size() == 0 && datatypes.size() > 0) { + for (int i = 0; i < ids.size(); i++) { + final String datatype = datatypes.get(i); + final AutofillValue value = values.get(i); + final RemoteViews fieldPresentation = presentations.get(i); + final RemoteViews fieldDialogPresentation = dialogPresentations.get(i); + final InlinePresentation fieldInlinePresentation = + i < inlinePresentationsSize ? inlinePresentations.get(i) : null; + final InlinePresentation fieldInlineTooltipPresentation = + i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; + final DatasetFieldFilter filter = filters.get(i); + builder.setLifeTheUniverseAndEverything( + datatype, value, fieldPresentation, fieldInlinePresentation, + fieldInlineTooltipPresentation, filter, fieldDialogPresentation); + } + } else { + for (int i = 0; i < ids.size(); i++) { + final AutofillId id = ids.get(i); + final AutofillValue value = values.get(i); + final RemoteViews fieldPresentation = presentations.get(i); + final RemoteViews fieldDialogPresentation = dialogPresentations.get(i); + final InlinePresentation fieldInlinePresentation = + i < inlinePresentationsSize ? inlinePresentations.get(i) : null; + final InlinePresentation fieldInlineTooltipPresentation = + i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; + final DatasetFieldFilter filter = filters.get(i); + builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, + fieldInlinePresentation, fieldInlineTooltipPresentation, filter, + fieldDialogPresentation); + } } builder.setAuthentication(authentication); builder.setId(datasetId); diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index eb5e893416c7f..4a848dd864638 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -140,6 +140,19 @@ public final class FillRequest implements Parcelable { */ private final @NonNull List mFillContexts; + /** + * Sends a list of datatypes for the Autofill Provider. + * + * If this is populated, Autofill Provider should return data + * for the autofill hints requested here, + * even though the Autofill Provider may not have detected these types. + * The hints would be part of HintConstants: + * https://developer.android.com/reference/androidx/autofill/HintConstants + * + * This is populated if the platform's field detection is enabled. + */ + private final @NonNull List mHints; + /** * Gets the latest client state bundle set by the service in a * {@link FillResponse.Builder#setClientState(Bundle) fill response}. @@ -196,6 +209,7 @@ public final class FillRequest implements Parcelable { private void onConstructed() { Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts"); + Preconditions.checkCollectionElementsNotNull(mHints, "hints"); } @@ -269,6 +283,11 @@ public final class FillRequest implements Parcelable { *

Note: Starting on Android {@link android.os.Build.VERSION_CODES#Q}, it could also * include contexts from requests whose {@link SaveInfo} had the * {@link SaveInfo#FLAG_DELAY_SAVE} flag. + * @param hints + * Autofill Provider should return data for the autofill hints requested here, + * even though the Autofill Provider may not have detected these types. + * The hints would be part of HintConstants: + * https://developer.android.com/reference/androidx/autofill/HintConstants * @param clientState * Gets the latest client state bundle set by the service in a * {@link FillResponse.Builder#setClientState(Bundle) fill response}. @@ -312,6 +331,7 @@ public final class FillRequest implements Parcelable { public FillRequest( int id, @NonNull List fillContexts, + @NonNull List hints, @Nullable Bundle clientState, @RequestFlags int flags, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, @@ -320,6 +340,9 @@ public final class FillRequest implements Parcelable { this.mFillContexts = fillContexts; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mFillContexts); + this.mHints = hints; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHints); this.mClientState = clientState; this.mFlags = flags; @@ -359,6 +382,17 @@ public final class FillRequest implements Parcelable { return mFillContexts; } + /** + * Autofill Provider should return data for the autofill hints requested here, + * even though the Autofill Provider may not have detected these types. + * The hints would be part of HintConstants: + * https://developer.android.com/reference/androidx/autofill/HintConstants + */ + @DataClass.Generated.Member + public @NonNull List getHints() { + return mHints; + } + /** * Gets the latest client state bundle set by the service in a * {@link FillResponse.Builder#setClientState(Bundle) fill response}. @@ -433,6 +467,7 @@ public final class FillRequest implements Parcelable { return "FillRequest { " + "id = " + mId + ", " + "fillContexts = " + mFillContexts + ", " + + "hints = " + mHints + ", " + "clientState = " + mClientState + ", " + "flags = " + requestFlagsToString(mFlags) + ", " + "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " + @@ -447,12 +482,13 @@ public final class FillRequest implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; - if (mClientState != null) flg |= 0x4; - if (mInlineSuggestionsRequest != null) flg |= 0x10; - if (mDelayedFillIntentSender != null) flg |= 0x20; + if (mClientState != null) flg |= 0x8; + if (mInlineSuggestionsRequest != null) flg |= 0x20; + if (mDelayedFillIntentSender != null) flg |= 0x40; dest.writeByte(flg); dest.writeInt(mId); dest.writeParcelableList(mFillContexts, flags); + dest.writeStringList(mHints); if (mClientState != null) dest.writeBundle(mClientState); dest.writeInt(mFlags); if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags); @@ -474,15 +510,20 @@ public final class FillRequest implements Parcelable { int id = in.readInt(); List fillContexts = new ArrayList<>(); in.readParcelableList(fillContexts, FillContext.class.getClassLoader()); - Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle(); + List hints = new ArrayList<>(); + in.readStringList(hints); + Bundle clientState = (flg & 0x8) == 0 ? null : in.readBundle(); int flags = in.readInt(); - InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR); - IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR); + InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x20) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR); + IntentSender delayedFillIntentSender = (flg & 0x40) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR); this.mId = id; this.mFillContexts = fillContexts; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mFillContexts); + this.mHints = hints; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHints); this.mClientState = clientState; this.mFlags = flags; @@ -517,10 +558,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1675460688829L, + time = 1675711417112L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java", - inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List mFillContexts\nprivate final @android.annotation.NonNull java.util.List mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 385b0aa8b867a..fa7ace3bbe0d7 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -34,6 +34,7 @@ import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.service.assist.classification.FieldClassification; import android.view.autofill.AutofillId; import android.widget.RemoteViews; @@ -45,6 +46,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Response for an {@link @@ -113,6 +115,7 @@ public final class FillResponse implements Parcelable { private final @StringRes int mServiceDisplayNameResourceId; private final boolean mShowFillDialogIcon; private final boolean mShowSaveDialogIcon; + private final @Nullable FieldClassification[] mDetectedFieldTypes; private FillResponse(@NonNull Builder builder) { mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; @@ -140,6 +143,14 @@ public final class FillResponse implements Parcelable { mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId; mShowFillDialogIcon = builder.mShowFillDialogIcon; mShowSaveDialogIcon = builder.mShowSaveDialogIcon; + mDetectedFieldTypes = builder.mDetectedFieldTypes; + } + + /** @hide */ + @TestApi + @NonNull + public Set getDetectedFieldClassifications() { + return Set.of(mDetectedFieldTypes); } /** @hide */ @@ -312,6 +323,28 @@ public final class FillResponse implements Parcelable { private int mServiceDisplayNameResourceId; private boolean mShowFillDialogIcon = true; private boolean mShowSaveDialogIcon = true; + private FieldClassification[] mDetectedFieldTypes; + + /** + * Adds a new {@link FieldClassification} to this response, to + * help the platform provide more accurate detection results. + * + * Call this when a field has been detected with a type. + * + * Altough similiarly named with {@link setFieldClassificationIds}, + * it provides a different functionality - setFieldClassificationIds should + * be used when a field is only suspected to be Autofillable. + * This method should be used when a field is certainly Autofillable + * with a certain type. + */ + @NonNull + public Builder setDetectedFieldClassifications( + @NonNull Set fieldInfos) { + throwIfDestroyed(); + throwIfDisableAutofillCalled(); + mDetectedFieldTypes = fieldInfos.toArray(new FieldClassification[0]); + return this; + } /** * Triggers a custom UI before autofilling the screen with any data set in this @@ -1122,6 +1155,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); parcel.writeParcelableArray(mFieldClassificationIds, flags); + parcel.writeParcelableArray(mDetectedFieldTypes, flags); parcel.writeInt(mIconResourceId); parcel.writeInt(mServiceDisplayNameResourceId); parcel.writeBoolean(mShowFillDialogIcon); @@ -1192,6 +1226,12 @@ public final class FillResponse implements Parcelable { builder.setFieldClassificationIds(fieldClassifactionIds); } + final FieldClassification[] detectedFields = + parcel.readParcelableArray(null, FieldClassification.class); + if (detectedFields != null) { + builder.setDetectedFieldClassifications(Set.of(detectedFields)); + } + builder.setIconResourceId(parcel.readInt()); builder.setServiceDisplayNameResourceId(parcel.readInt()); builder.setShowFillDialogIcon(parcel.readBoolean()); diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 5fe1d4f5ca5fe..828e466783d1e 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -308,7 +308,7 @@ public final class SaveInfo implements Parcelable { * username field, another for password). */ // TODO(b/113281366): improve documentation: add example, document relationship with other - // flagss, etc... + // flags, etc... public static final int FLAG_DELAY_SAVE = 0x4; /** @hide */ @@ -777,17 +777,13 @@ public final class SaveInfo implements Parcelable { /** * Builds a new {@link SaveInfo} instance. * - * @throws IllegalStateException if no - * {@link #Builder(int, AutofillId[]) required ids}, + * If no {@link #Builder(int, AutofillId[]) required ids}, * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE} - * were set + * were set, Save Dialog will only be triggered if platform detection is enabled, which + * is indicated when {@link FillRequest.getHints()} is not empty. */ public SaveInfo build() { throwIfDestroyed(); - Preconditions.checkState( - !ArrayUtils.isEmpty(mRequiredIds) || !ArrayUtils.isEmpty(mOptionalIds) - || (mFlags & FLAG_DELAY_SAVE) != 0, - "must have at least one required or optional id or FLAG_DELAYED_SAVE"); mDestroyed = true; return new SaveInfo(this); } diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index e7c610b8bd8de..864ba92ca887c 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -81,6 +81,12 @@ public class AutofillFeatureFlags { public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled"; + /** + * Indicates that PCC Autofill detection feature is enabled or not. + */ + public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS = + "pcc_classification_hints"; + /** * Sets the autofill hints allowed list for the fields that can trigger the fill dialog * feature at Activity starting. @@ -178,6 +184,22 @@ public class AutofillFeatureFlags { public static final String DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED = "pcc_classification_enabled"; + /** + * Give preference to autofill provider's detection. + * @hide + */ + public static final String DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC = "prefer_provider_over_pcc"; + + + /** + * Use data from secondary source if primary not present . + * For eg: if we prefer PCC over provider, and PCC detection didn't classify a field, however, + * autofill provider did, this flag would decide whether we use that result, and show some + * presentation for that particular field. + * @hide + */ + public static final String DEVICE_CONFIG_PCC_USE_FALLBACK = "pcc_use_fallback"; + // END AUTOFILL PCC CLASSIFICATION FLAGS @@ -190,9 +212,11 @@ public class AutofillFeatureFlags { "autofill_inline_tooltip_first_show_delay"; private static final String DIALOG_HINTS_DELIMITER = ":"; + private static final String PCC_HINTS_DELIMITER = ","; private static final boolean DEFAULT_HAS_FILL_DIALOG_UI_FEATURE = false; private static final String DEFAULT_FILL_DIALOG_ENABLED_HINTS = ""; + private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = ""; // CREDENTIAL MANAGER DEFAULTS @@ -206,7 +230,8 @@ public class AutofillFeatureFlags { // AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS // Default for whether the pcc classification is enabled for autofill. - private static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false; + /** @hide */ + public static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false; // END AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS @@ -224,6 +249,25 @@ public class AutofillFeatureFlags { DEFAULT_HAS_FILL_DIALOG_UI_FEATURE); } + /** + * The list of datatypes that is supported by framework + * detection. + * + * @hide + */ + public static String[] getTypeHintsForProvider() { + final String typeHints = DeviceConfig.getString( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS, + DEFAULT_PCC_FEATURE_PROVIDER_HINTS); + if (TextUtils.isEmpty(typeHints)) { + return new String[0]; + } + + return ArrayUtils.filter(typeHints.split(PCC_HINTS_DELIMITER), String[]::new, + (str) -> !TextUtils.isEmpty(str)); + } + /** * Gets fill dialog enabled hints. * diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 6b61e978ea7b6..b991a026faa1e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -193,6 +193,32 @@ public final class AutofillManagerService final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState(); + /** + * Lock used to synchronize access to flags. + */ + private final Object mFlagLock = new Object(); + + // Flag holders for Autofill PCC classification + + @GuardedBy("mFlagLock") + private boolean mPccClassificationEnabled; + + @GuardedBy("mFlagLock") + private boolean mPccPreferProviderOverPcc; + + @GuardedBy("mFlagLock") + private boolean mPccUseFallbackDetection; + + @GuardedBy("mFlagLock") + private String mPccProviderHints; + + // Default flag values for Autofill PCC + + private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = ""; + private static final boolean DEFAULT_PREFER_PROVIDER_OVER_PCC = true; + + private static final boolean DEFAULT_PCC_USE_FALLBACK = true; + public AutofillManagerService(Context context) { super(context, new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE), @@ -302,6 +328,10 @@ public final class AutofillManagerService case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES: case AutofillFeatureFlags.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT: case AutofillFeatureFlags.DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT: + case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED: + case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS: + case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC: + case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK: setDeviceConfigProperties(); break; case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: @@ -579,13 +609,38 @@ public final class AutofillManagerService AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES, AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM); if (verbose) { - Slog.v(mTag, "setDeviceConfigProperties(): " + Slog.v(mTag, "setDeviceConfigProperties() for AugmentedAutofill: " + "augmentedIdleTimeout=" + mAugmentedServiceIdleUnbindTimeoutMs + ", augmentedRequestTimeout=" + mAugmentedServiceRequestTimeoutMs + ", smartSuggestionMode=" + getSmartSuggestionModeToString(mSupportedSmartSuggestionModes)); } } + synchronized (mFlagLock) { + mPccClassificationEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED, + AutofillFeatureFlags.DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED); + mPccPreferProviderOverPcc = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC, + DEFAULT_PREFER_PROVIDER_OVER_PCC); + mPccUseFallbackDetection = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK, + DEFAULT_PCC_USE_FALLBACK); + mPccProviderHints = DeviceConfig.getString( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS, + DEFAULT_PCC_FEATURE_PROVIDER_HINTS); + if (verbose) { + Slog.v(mTag, "setDeviceConfigProperties() for PCC: " + + "mPccClassificationEnabled=" + mPccClassificationEnabled + + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc + + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection + + ", mPccProviderHints=" + mPccProviderHints); + } + } } private void updateCachedServices() { @@ -791,6 +846,46 @@ public final class AutofillManagerService } } + /** + * Whether the Autofill PCC Classification feature is enabled. + */ + public boolean isPccClassificationEnabled() { + synchronized (mFlagLock) { + return mPccClassificationEnabled; + } + } + + /** + * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets. + */ + public boolean preferProviderOverPcc() { + synchronized (mFlagLock) { + return mPccPreferProviderOverPcc; + } + } + + /** + * Whether to use the fallback for detection. + * If true, use data from secondary source if primary not present . + * For eg: if we prefer PCC over provider, and PCC detection didn't classify a field, however, + * autofill provider did, this flag would decide whether we use that result, and show some + * presentation for that particular field. + */ + public boolean shouldUsePccFallback() { + synchronized (mFlagLock) { + return mPccUseFallbackDetection; + } + } + + /** + * Provides Autofill Hints that would be requested by the service from the Autofill Provider. + */ + public String getPccProviderHints() { + synchronized (mFlagLock) { + return mPccProviderHints; + } + } + @Nullable @VisibleForTesting static Map getAllowedCompatModePackages(String setting) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 2b529bfe5e32c..c5c92885406f4 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -170,6 +170,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState "android.service.autofill.action.DELAYED_FILL"; private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; + private static final String PCC_HINTS_DELIMITER = ","; + final Object mLock; private final AutofillManagerServiceImpl mService; @@ -564,6 +566,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mPendingInlineSuggestionsRequest.isServiceSupported()) { mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), mPendingFillRequest.getClientState(), mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest, @@ -672,8 +675,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList contexts = mergePreviousSessionLocked(/* forSave= */ false); + final List hints = getTypeHintsForProvider(); + mDelayedFillPendingIntent = createPendingIntent(requestId); - request = new FillRequest(requestId, contexts, mClientState, flags, + request = new FillRequest(requestId, contexts, hints, mClientState, flags, /*inlineSuggestionsRequest=*/ null, /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null ? null @@ -704,6 +709,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + /** + * Get the list of valid autofill hint types from Device flags + * Returns empty list if PCC is off or no types available + */ + private List getTypeHintsForProvider() { + if (!mService.getMaster().isPccClassificationEnabled()) { + return Collections.EMPTY_LIST; + } + final String typeHints = mService.getMaster().getPccProviderHints(); + if (TextUtils.isEmpty(typeHints)) { + return new ArrayList<>(); + } + + return List.of(typeHints.split(PCC_HINTS_DELIMITER)); + } + /** * Assist Data Receiver for PCC */