Loading api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37137,6 +37137,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback { api/system-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -40232,6 +40232,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback { api/test-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37426,6 +37426,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback { core/java/android/service/autofill/Dataset.java +153 −34 Original line number Diff line number Diff line Loading @@ -29,32 +29,77 @@ import android.widget.RemoteViews; import com.android.internal.util.Preconditions; import java.io.Serializable; import java.util.ArrayList; import java.util.regex.Pattern; /** * A dataset object represents a group of key/value pairs used to autofill parts of a screen. * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a * screen. * * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of * {@link AutofillId} and {@link AutofillValue} respectively); and one or more * {@link RemoteViews presentation} for these pairs (a pair could have its own * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse} * <a name="BasicUsage"></a> * <h3>Basic usage</h3> * * <p>In its simplest form, a dataset contains one or more fields (comprised of * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields * (each field could have its own {@link RemoteViews presentation}, or use the default * {@link RemoteViews presentation} associated with the whole dataset). * * <p>When an autofill service returns datasets in a {@link FillResponse} * and the screen input is focused in a view that is present in at least one of these datasets, * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of * the Android System displays a UI containing the {@link RemoteViews presentation} of * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a * dataset from the affordance, all views in that dataset are autofilled. * dataset from the UI, all views in that dataset are autofilled. * * <a name="Authentication"></a> * <h3>Dataset authentication</h3> * * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates * the dataset—in that case, when a dataset is selected by the user, the Android System * launches an intent set by the service to "unlock" the dataset. * * <p>For example, when a data set contains credit card information (such as number, * expiration date, and verification code), you could provide a dataset presentation saying * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking * the user to enter the credit card code, and if the user enters a valid code, you could then * "unlock" the dataset. * * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example, * if the activity being autofilled is an account creation screen, you could use an authenticated * dataset to automatically generate a random password for the user. * * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}. * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset * authentication mechanism. * * @see android.service.autofill.AutofillService for more information and examples about the * role of datasets in the autofill workflow. * <a name="Filtering"></a> * <h3>Filtering</h3> * <p>The autofill UI automatically changes which values are shown based on value of the view * anchoring it, following the rules below: * <ol> * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not * {@link AutofillValue#isText() text} or is empty, all datasets are shown. * <li>Datasets that have a filter regex (set through * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose * regex matches the view's text value converted to lower case are shown. * <li>Datasets that do not require authentication, have a field value that is * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts * with the lower case value of the view's text are shown. * <li>All other datasets are hidden. * </ol> * * <a name="MoreInfo"></a> * <h3>More information</h3> * <p>See {@link android.service.autofill.AutofillService} for more information and examples about * the role of datasets in the autofill workflow. */ public final class Dataset implements Parcelable { private final ArrayList<AutofillId> mFieldIds; private final ArrayList<AutofillValue> mFieldValues; private final ArrayList<RemoteViews> mFieldPresentations; private final ArrayList<Pattern> mFieldFilters; private final RemoteViews mPresentation; private final IntentSender mAuthentication; @Nullable String mId; Loading @@ -63,6 +108,7 @@ public final class Dataset implements Parcelable { mFieldIds = builder.mFieldIds; mFieldValues = builder.mFieldValues; mFieldPresentations = builder.mFieldPresentations; mFieldFilters = builder.mFieldFilters; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; mId = builder.mId; Loading @@ -84,6 +130,12 @@ public final class Dataset implements Parcelable { return customPresentation != null ? customPresentation : mPresentation; } /** @hide */ @Nullable public Pattern getFilter(int index) { return mFieldFilters.get(index); } /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; Loading @@ -103,6 +155,8 @@ public final class Dataset implements Parcelable { .append(", fieldValues=").append(mFieldValues) .append(", fieldPresentations=") .append(mFieldPresentations == null ? 0 : mFieldPresentations.size()) .append(", fieldFilters=") .append(mFieldFilters == null ? 0 : mFieldFilters.size()) .append(", hasPresentation=").append(mPresentation != null) .append(", hasAuthentication=").append(mAuthentication != null) .append(']').toString(); Loading @@ -127,6 +181,7 @@ public final class Dataset implements Parcelable { private ArrayList<AutofillId> mFieldIds; private ArrayList<AutofillValue> mFieldValues; private ArrayList<RemoteViews> mFieldPresentations; private ArrayList<Pattern> mFieldFilters; private RemoteViews mPresentation; private IntentSender mAuthentication; private boolean mDestroyed; Loading Loading @@ -182,12 +237,12 @@ public final class Dataset implements Parcelable { * credit card information without the CVV for the data set in the {@link FillResponse * response} then the returned data set should contain the CVV entry. * * <p><b>NOTE:</b> Do not make the provided pending intent * <p><b>Note:</b> Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * @return This builder. * @return this builder. * * @see android.app.PendingIntent */ Loading @@ -214,11 +269,10 @@ public final class Dataset implements Parcelable { * * @param id id for this dataset or {@code null} to unset. * * @return This builder. * @return this builder. */ public @NonNull Builder setId(@Nullable String id) { throwIfDestroyed(); mId = id; return this; } Loading @@ -230,17 +284,16 @@ public final class Dataset implements Parcelable { * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs an authentication and you have no access to the value. * @return This builder. * the dataset needs authentication and you have no access to the value. * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { throwIfDestroyed(); if (mPresentation == null) { throw new IllegalStateException("Dataset presentation not set on constructor"); } setValueAndPresentation(id, value, null); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); setLifeTheUniverseAndEverything(id, value, null, null); return this; } Loading @@ -250,23 +303,81 @@ public final class Dataset implements Parcelable { * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value value to be auto filled. Pass {@code null} if you do not have the value * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs an authentication and you have no access to the value. * Filtering matches any user typed string to {@code null} values. * @param presentation The presentation used to visualize this field. * @return This builder. * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @return this builder. * */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setValueAndPresentation(id, value, presentation); setLifeTheUniverseAndEverything(id, value, presentation, null); return this; } /** * Sets the value of a field using an <a href="#Filtering">explicit filter</a>. * * <p>This method is typically used when the dataset is not authenticated and the field * value is not {@link AutofillValue#isText() text} but the service still wants to allow * the user to filter it out. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI. * * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull Pattern filter) { throwIfDestroyed(); Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); setLifeTheUniverseAndEverything(id, value, null, filter); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a <a href="#Filtering">explicit filter</a>. * * <p>Typically used to allow filtering on * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For * example, if the dataset represents a credit card number and the service does not want to * show the "Tap to authenticate" message until the user tapped 4 digits, in which case * the filter would be {@code Pattern.compile("\\d.{4,}")}. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @param filter regex used to determine if the dataset should be shown in the autofill UI. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull Pattern filter, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, filter); return this; } private void setValueAndPresentation(AutofillId id, AutofillValue value, RemoteViews presentation) { private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable Pattern filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); Loading @@ -279,10 +390,12 @@ public final class Dataset implements Parcelable { mFieldIds = new ArrayList<>(); mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); } mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldFilters.add(filter); } /** Loading @@ -290,8 +403,9 @@ public final class Dataset implements Parcelable { * * <p>You should not interact with this builder once this method is called. * * <p>It is required that you specify at least one field before calling this method. It's * also mandatory to provide a presentation view to visualize the data set in the UI. * @throws IllegalStateException if no field was set (through * {@link #setValue(AutofillId, AutofillValue)} or * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}). * * @return The built dataset. */ Loading @@ -299,7 +413,7 @@ public final class Dataset implements Parcelable { throwIfDestroyed(); mDestroyed = true; if (mFieldIds == null) { throw new IllegalArgumentException("at least one value must be set"); throw new IllegalStateException("at least one value must be set"); } return new Dataset(this); } Loading @@ -326,6 +440,7 @@ public final class Dataset implements Parcelable { parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); parcel.writeParcelableList(mFieldPresentations, flags); parcel.writeSerializable(mFieldFilters); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); } Loading @@ -345,6 +460,9 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList<RemoteViews> presentations = new ArrayList<>(); parcel.readParcelableList(presentations, null); @SuppressWarnings("unchecked") final ArrayList<Serializable> filters = (ArrayList<Serializable>) parcel.readSerializable(); final int idCount = (ids != null) ? ids.size() : 0; final int valueCount = (values != null) ? values.size() : 0; for (int i = 0; i < idCount; i++) { Loading @@ -352,7 +470,8 @@ public final class Dataset implements Parcelable { final AutofillValue value = (valueCount > i) ? values.get(i) : null; final RemoteViews fieldPresentation = presentations.isEmpty() ? null : presentations.get(i); builder.setValueAndPresentation(id, value, fieldPresentation); final Pattern filter = (Pattern) filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter); } builder.setAuthentication(parcel.readParcelable(null)); builder.setId(parcel.readString()); Loading services/autofill/java/com/android/server/autofill/ui/FillUi.java +27 −17 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; final class FillUi { private static final String TAG = "FillUi"; Loading Loading @@ -164,15 +165,18 @@ final class FillUi { Slog.e(TAG, "Error inflating remote views", e); continue; } final AutofillValue value = dataset.getFieldValues().get(index); final Pattern filter = dataset.getFilter(index); String valueText = null; if (filter == null) { final AutofillValue value = dataset.getFieldValues().get(index); // If the dataset needs auth - don't add its text to allow guessing // its content based on how filtering behaves. if (value != null && value.isText() && dataset.getAuthentication() == null) { valueText = value.getTextValue().toString().toLowerCase(); } } items.add(new ViewItem(dataset, valueText, view)); items.add(new ViewItem(dataset, filter, valueText, view)); } } Loading Loading @@ -331,11 +335,17 @@ final class FillUi { private final String mValue; private final Dataset mDataset; private final View mView; private final Pattern mFilter; ViewItem(Dataset dataset, String value, View view) { ViewItem(Dataset dataset, Pattern filter, String value, View view) { mDataset = dataset; mValue = value; mView = view; mFilter = filter; } public Pattern getFilter() { return mFilter; } public View getView() { Loading @@ -349,12 +359,6 @@ final class FillUi { public String getValue() { return mValue; } @Override public String toString() { // Used for filtering in the adapter return mValue; } } private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub { Loading Loading @@ -516,10 +520,16 @@ final class FillUi { for (int i = 0; i < itemCount; i++) { final ViewItem item = mAllItems.get(i); final String value = item.getValue(); // No value, i.e. null, matches any filter if ((value == null && item.mDataset.getAuthentication() == null) || (value != null && value.toLowerCase().startsWith(constraintLowerCase))) { final Pattern filter = item.getFilter(); final boolean matches; if (filter != null) { matches = filter.matcher(constraintLowerCase).matches(); } else { matches = (value == null) ? (item.mDataset.getAuthentication() == null) : value.toLowerCase().startsWith(constraintLowerCase); } if (matches) { filteredItems.add(item); } } Loading Loading
api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37137,6 +37137,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback {
api/system-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -40232,6 +40232,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback {
api/test-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -37426,6 +37426,8 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setId(java.lang.String); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } public final class FillCallback {
core/java/android/service/autofill/Dataset.java +153 −34 Original line number Diff line number Diff line Loading @@ -29,32 +29,77 @@ import android.widget.RemoteViews; import com.android.internal.util.Preconditions; import java.io.Serializable; import java.util.ArrayList; import java.util.regex.Pattern; /** * A dataset object represents a group of key/value pairs used to autofill parts of a screen. * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a * screen. * * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of * {@link AutofillId} and {@link AutofillValue} respectively); and one or more * {@link RemoteViews presentation} for these pairs (a pair could have its own * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse} * <a name="BasicUsage"></a> * <h3>Basic usage</h3> * * <p>In its simplest form, a dataset contains one or more fields (comprised of * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields * (each field could have its own {@link RemoteViews presentation}, or use the default * {@link RemoteViews presentation} associated with the whole dataset). * * <p>When an autofill service returns datasets in a {@link FillResponse} * and the screen input is focused in a view that is present in at least one of these datasets, * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of * the Android System displays a UI containing the {@link RemoteViews presentation} of * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a * dataset from the affordance, all views in that dataset are autofilled. * dataset from the UI, all views in that dataset are autofilled. * * <a name="Authentication"></a> * <h3>Dataset authentication</h3> * * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates * the dataset—in that case, when a dataset is selected by the user, the Android System * launches an intent set by the service to "unlock" the dataset. * * <p>For example, when a data set contains credit card information (such as number, * expiration date, and verification code), you could provide a dataset presentation saying * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking * the user to enter the credit card code, and if the user enters a valid code, you could then * "unlock" the dataset. * * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example, * if the activity being autofilled is an account creation screen, you could use an authenticated * dataset to automatically generate a random password for the user. * * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}. * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset * authentication mechanism. * * @see android.service.autofill.AutofillService for more information and examples about the * role of datasets in the autofill workflow. * <a name="Filtering"></a> * <h3>Filtering</h3> * <p>The autofill UI automatically changes which values are shown based on value of the view * anchoring it, following the rules below: * <ol> * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not * {@link AutofillValue#isText() text} or is empty, all datasets are shown. * <li>Datasets that have a filter regex (set through * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose * regex matches the view's text value converted to lower case are shown. * <li>Datasets that do not require authentication, have a field value that is * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts * with the lower case value of the view's text are shown. * <li>All other datasets are hidden. * </ol> * * <a name="MoreInfo"></a> * <h3>More information</h3> * <p>See {@link android.service.autofill.AutofillService} for more information and examples about * the role of datasets in the autofill workflow. */ public final class Dataset implements Parcelable { private final ArrayList<AutofillId> mFieldIds; private final ArrayList<AutofillValue> mFieldValues; private final ArrayList<RemoteViews> mFieldPresentations; private final ArrayList<Pattern> mFieldFilters; private final RemoteViews mPresentation; private final IntentSender mAuthentication; @Nullable String mId; Loading @@ -63,6 +108,7 @@ public final class Dataset implements Parcelable { mFieldIds = builder.mFieldIds; mFieldValues = builder.mFieldValues; mFieldPresentations = builder.mFieldPresentations; mFieldFilters = builder.mFieldFilters; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; mId = builder.mId; Loading @@ -84,6 +130,12 @@ public final class Dataset implements Parcelable { return customPresentation != null ? customPresentation : mPresentation; } /** @hide */ @Nullable public Pattern getFilter(int index) { return mFieldFilters.get(index); } /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; Loading @@ -103,6 +155,8 @@ public final class Dataset implements Parcelable { .append(", fieldValues=").append(mFieldValues) .append(", fieldPresentations=") .append(mFieldPresentations == null ? 0 : mFieldPresentations.size()) .append(", fieldFilters=") .append(mFieldFilters == null ? 0 : mFieldFilters.size()) .append(", hasPresentation=").append(mPresentation != null) .append(", hasAuthentication=").append(mAuthentication != null) .append(']').toString(); Loading @@ -127,6 +181,7 @@ public final class Dataset implements Parcelable { private ArrayList<AutofillId> mFieldIds; private ArrayList<AutofillValue> mFieldValues; private ArrayList<RemoteViews> mFieldPresentations; private ArrayList<Pattern> mFieldFilters; private RemoteViews mPresentation; private IntentSender mAuthentication; private boolean mDestroyed; Loading Loading @@ -182,12 +237,12 @@ public final class Dataset implements Parcelable { * credit card information without the CVV for the data set in the {@link FillResponse * response} then the returned data set should contain the CVV entry. * * <p><b>NOTE:</b> Do not make the provided pending intent * <p><b>Note:</b> Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * @return This builder. * @return this builder. * * @see android.app.PendingIntent */ Loading @@ -214,11 +269,10 @@ public final class Dataset implements Parcelable { * * @param id id for this dataset or {@code null} to unset. * * @return This builder. * @return this builder. */ public @NonNull Builder setId(@Nullable String id) { throwIfDestroyed(); mId = id; return this; } Loading @@ -230,17 +284,16 @@ public final class Dataset implements Parcelable { * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs an authentication and you have no access to the value. * @return This builder. * the dataset needs authentication and you have no access to the value. * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { throwIfDestroyed(); if (mPresentation == null) { throw new IllegalStateException("Dataset presentation not set on constructor"); } setValueAndPresentation(id, value, null); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); setLifeTheUniverseAndEverything(id, value, null, null); return this; } Loading @@ -250,23 +303,81 @@ public final class Dataset implements Parcelable { * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value value to be auto filled. Pass {@code null} if you do not have the value * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs an authentication and you have no access to the value. * Filtering matches any user typed string to {@code null} values. * @param presentation The presentation used to visualize this field. * @return This builder. * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @return this builder. * */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setValueAndPresentation(id, value, presentation); setLifeTheUniverseAndEverything(id, value, presentation, null); return this; } /** * Sets the value of a field using an <a href="#Filtering">explicit filter</a>. * * <p>This method is typically used when the dataset is not authenticated and the field * value is not {@link AutofillValue#isText() text} but the service still wants to allow * the user to filter it out. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI. * * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull Pattern filter) { throwIfDestroyed(); Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); setLifeTheUniverseAndEverything(id, value, null, filter); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a <a href="#Filtering">explicit filter</a>. * * <p>Typically used to allow filtering on * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For * example, if the dataset represents a credit card number and the service does not want to * show the "Tap to authenticate" message until the user tapped 4 digits, in which case * the filter would be {@code Pattern.compile("\\d.{4,}")}. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @param filter regex used to determine if the dataset should be shown in the autofill UI. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull Pattern filter, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, filter); return this; } private void setValueAndPresentation(AutofillId id, AutofillValue value, RemoteViews presentation) { private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable Pattern filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); Loading @@ -279,10 +390,12 @@ public final class Dataset implements Parcelable { mFieldIds = new ArrayList<>(); mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); } mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldFilters.add(filter); } /** Loading @@ -290,8 +403,9 @@ public final class Dataset implements Parcelable { * * <p>You should not interact with this builder once this method is called. * * <p>It is required that you specify at least one field before calling this method. It's * also mandatory to provide a presentation view to visualize the data set in the UI. * @throws IllegalStateException if no field was set (through * {@link #setValue(AutofillId, AutofillValue)} or * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}). * * @return The built dataset. */ Loading @@ -299,7 +413,7 @@ public final class Dataset implements Parcelable { throwIfDestroyed(); mDestroyed = true; if (mFieldIds == null) { throw new IllegalArgumentException("at least one value must be set"); throw new IllegalStateException("at least one value must be set"); } return new Dataset(this); } Loading @@ -326,6 +440,7 @@ public final class Dataset implements Parcelable { parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); parcel.writeParcelableList(mFieldPresentations, flags); parcel.writeSerializable(mFieldFilters); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); } Loading @@ -345,6 +460,9 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList<RemoteViews> presentations = new ArrayList<>(); parcel.readParcelableList(presentations, null); @SuppressWarnings("unchecked") final ArrayList<Serializable> filters = (ArrayList<Serializable>) parcel.readSerializable(); final int idCount = (ids != null) ? ids.size() : 0; final int valueCount = (values != null) ? values.size() : 0; for (int i = 0; i < idCount; i++) { Loading @@ -352,7 +470,8 @@ public final class Dataset implements Parcelable { final AutofillValue value = (valueCount > i) ? values.get(i) : null; final RemoteViews fieldPresentation = presentations.isEmpty() ? null : presentations.get(i); builder.setValueAndPresentation(id, value, fieldPresentation); final Pattern filter = (Pattern) filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter); } builder.setAuthentication(parcel.readParcelable(null)); builder.setId(parcel.readString()); Loading
services/autofill/java/com/android/server/autofill/ui/FillUi.java +27 −17 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; final class FillUi { private static final String TAG = "FillUi"; Loading Loading @@ -164,15 +165,18 @@ final class FillUi { Slog.e(TAG, "Error inflating remote views", e); continue; } final AutofillValue value = dataset.getFieldValues().get(index); final Pattern filter = dataset.getFilter(index); String valueText = null; if (filter == null) { final AutofillValue value = dataset.getFieldValues().get(index); // If the dataset needs auth - don't add its text to allow guessing // its content based on how filtering behaves. if (value != null && value.isText() && dataset.getAuthentication() == null) { valueText = value.getTextValue().toString().toLowerCase(); } } items.add(new ViewItem(dataset, valueText, view)); items.add(new ViewItem(dataset, filter, valueText, view)); } } Loading Loading @@ -331,11 +335,17 @@ final class FillUi { private final String mValue; private final Dataset mDataset; private final View mView; private final Pattern mFilter; ViewItem(Dataset dataset, String value, View view) { ViewItem(Dataset dataset, Pattern filter, String value, View view) { mDataset = dataset; mValue = value; mView = view; mFilter = filter; } public Pattern getFilter() { return mFilter; } public View getView() { Loading @@ -349,12 +359,6 @@ final class FillUi { public String getValue() { return mValue; } @Override public String toString() { // Used for filtering in the adapter return mValue; } } private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub { Loading Loading @@ -516,10 +520,16 @@ final class FillUi { for (int i = 0; i < itemCount; i++) { final ViewItem item = mAllItems.get(i); final String value = item.getValue(); // No value, i.e. null, matches any filter if ((value == null && item.mDataset.getAuthentication() == null) || (value != null && value.toLowerCase().startsWith(constraintLowerCase))) { final Pattern filter = item.getFilter(); final boolean matches; if (filter != null) { matches = filter.matcher(constraintLowerCase).matches(); } else { matches = (value == null) ? (item.mDataset.getAuthentication() == null) : value.toLowerCase().startsWith(constraintLowerCase); } if (matches) { filteredItems.add(item); } } Loading