summaryrefslogtreecommitdiff
path: root/android/service/autofill/Dataset.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/service/autofill/Dataset.java')
-rw-r--r--android/service/autofill/Dataset.java196
1 files changed, 158 insertions, 38 deletions
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index cb341b1d..ef9598aa 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -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&mdash;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;
@@ -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;
@@ -85,6 +131,12 @@ public final class Dataset implements Parcelable {
}
/** @hide */
+ @Nullable
+ public Pattern getFilter(int index) {
+ return mFieldFilters.get(index);
+ }
+
+ /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -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();
@@ -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;
@@ -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
*/
@@ -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;
}
@@ -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;
}
@@ -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);
@@ -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);
}
/**
@@ -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.
*/
@@ -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);
}
@@ -323,9 +437,10 @@ public final class Dataset implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
- parcel.writeTypedArrayList(mFieldIds, flags);
- parcel.writeTypedArrayList(mFieldValues, flags);
+ parcel.writeTypedList(mFieldIds, flags);
+ parcel.writeTypedList(mFieldValues, flags);
parcel.writeParcelableList(mFieldPresentations, flags);
+ parcel.writeSerializable(mFieldFilters);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
}
@@ -340,10 +455,14 @@ public final class Dataset implements Parcelable {
final Builder builder = (presentation == null)
? new Builder()
: new Builder(presentation);
- final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
- final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+ final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<AutofillValue> values =
+ 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++) {
@@ -351,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());