From 2ef19c1d73f89ca4718b5a8f0c2e7221621e844f Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 5 Jun 2017 11:32:32 -0700 Subject: [PATCH] Improved documentation for AutofillService package: - Moved (and expanded) overall documentation from FillResponse to AutofillService. - Improved SaveInfo documentation. - Improved FillRequest documentation. - Improved Dataset documentation. Bug: 37567048 Test: ran 'm -j doc-comment-check-docs' and checked resulting HTML Change-Id: I157893deac06a5ed5e1cb7fd082da485f227b9ee --- .../service/autofill/AutofillService.java | 228 ++++++++++++++++-- .../android/service/autofill/Dataset.java | 50 ++-- .../android/service/autofill/FillContext.java | 1 - .../android/service/autofill/FillRequest.java | 38 ++- .../service/autofill/FillResponse.java | 121 ++-------- .../android/service/autofill/SaveInfo.java | 102 +++++--- 6 files changed, 349 insertions(+), 191 deletions(-) diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 394bd0ac70f7c..a80ef032e68f9 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -19,24 +19,230 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; +import android.provider.Settings; + import com.android.internal.os.HandlerCaller; import android.annotation.SdkConstant; -import android.app.Activity; -import android.app.Service; -import android.content.Intent; +import android.app.Service;import android.content.Intent; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Looper; import android.util.Log; +import android.view.View; +import android.view.ViewStructure; +import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; import com.android.internal.os.SomeArgs; /** - * Top-level service of the current autofill service for a given user. + * An {@code AutofillService} is a service used to automatically fill the contents of the screen + * on behalf of a given user - for more information about autofill, read + * Autofill Framework. * - *

Apps providing autofill capabilities must extend this service. + *

An {@code AutofillService} is only bound to the Android System for autofill purposes if: + *

    + *
  1. It requires the {@code android.permission.BIND_AUTOFILL_SERVICE} permission in its + * manifest. + *
  2. The user explicitly enables it using Android Settings (the + * {@link Settings#ACTION_REQUEST_SET_AUTOFILL_SERVICE} intent can be used to launch such + * Settings screen). + *
+ * + *

Basic usage

+ * + *

The basic autofill process is defined by the workflow below: + *

    + *
  1. User focus an editable {@link View}. + *
  2. View calls {@link AutofillManager#notifyViewEntered(android.view.View)}. + *
  3. A {@link ViewStructure} representing all views in the screen is created. + *
  4. The Android System binds to the service and calls {@link #onConnected()}. + *
  5. The service receives the view structure through the + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)}. + *
  6. The service replies through {@link FillCallback#onSuccess(FillResponse)}. + *
  7. The Android System calls {@link #onDisconnected()} and unbinds from the + * {@code AutofillService}. + *
  8. The Android System displays an UI affordance with the options sent by the service. + *
  9. The user picks an option. + *
  10. The proper views are autofilled. + *
+ * + *

This workflow was designed to minimize the time the Android System is bound to the service; + * for each call, it: binds to service, waits for the reply, and unbinds right away. Furthermore, + * those calls are considered stateless: if the service needs to keep state between calls, it must + * do its own state management (keeping in mind that the service's process might be killed by the + * Android System when unbound; for example, if the device is running low in memory). + * + *

Typically, the + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will: + *

    + *
  1. Parse the view structure looking for autofillable views (for example, using + * {@link android.app.assist.AssistStructure.ViewNode#getAutofillHints()}. + *
  2. Match the autofillable views with the user's data. + *
  3. Create a {@link Dataset} for each set of user's data that match those fields. + *
  4. Fill the dataset(s) with the proper {@link AutofillId}s and {@link AutofillValue}s. + *
  5. Add the dataset(s) to the {@link FillResponse} passed to + * {@link FillCallback#onSuccess(FillResponse)}. + *
+ * + *

For example, for a login screen with username and password views where the user only has one + * account in the service, the response could be: + * + *

+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .build();
+ * 
+ * + *

But if the user had 2 accounts instead, the response could be: + * + *

+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ *         .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ *         .build())
+ *     .build();
+ * 
+ * + *

If the service does not find any autofillable view in the view structure, it should pass + * {@code null} to {@link FillCallback#onSuccess(FillResponse)}; if the service encountered an error + * processing the request, it should call {@link FillCallback#onFailure(CharSequence)}. For + * performance reasons, it's paramount that the service calls either + * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} for + * each {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} received - if it + * doesn't, the request will eventually time out and be discarded by the Android System. + * + *

Saving user data

+ * + *

If the service is also interested on saving the data filled by the user, it must set a + * {@link SaveInfo} object in the {@link FillResponse}. See {@link SaveInfo} for more details and + * examples. + * + *

User authentication

+ * + *

The service can provide an extra degree of security by requiring the user to authenticate + * before an app can be autofilled. The authentication is typically required in 2 scenarios: + *

+ * + *

When using authentication, it is recommended to encrypt only the sensitive data and leave + * labels unencrypted, so they can be used on presentation views. For example, if the user has a + * home and a work address, the {@code Home} and {@code Work} labels should be stored unencrypted + * (since they don't have any sensitive data) while the address data per se could be stored in an + * encrypted storage. Then when the user chooses the {@code Home} dataset, the platform starts + * the authentication flow, and the service can decrypt the sensitive data. + * + *

The authentication mechanism can also be used in scenarios where the service needs multiple + * steps to determine the datasets that can fill a screen. For example, when autofilling a financial + * app where the user has accounts for multiple banks, the workflow could be: + * + *

    + *
  1. The first {@link FillResponse} contains datasets with the credentials for the financial + * app, plus a "fake" dataset whose presentation says "Tap here for banking apps credentials". + *
  2. When the user selects the fake dataset, the service displays a dialog with available + * banking apps. + *
  3. When the user select a banking app, the service replies with a new {@link FillResponse} + * containing the datasets for that bank. + *
+ * + *

Another example of multiple-steps dataset selection is when the service stores the user + * credentials in "vaults": the first response would contain fake datasets with the vault names, + * and the subsequent response would contain the app credentials stored in that vault. + * + *

Data partitioning

+ * + *

The autofillable views in a screen should be grouped in logical groups called "partitions". + * Typical partitions are: + *

+ *

For security reasons, when a screen has more than one partition, it's paramount that the + * contents of a dataset do not spawn multiple partitions, specially when one of the partitions + * contains data that is not specific to the application being autofilled. For example, a dataset + * should not contain fields for username, password, and credit card information. The reason for + * this rule is that a malicious app could draft a view structure where the credit card fields + * are not visible, so when the user selects a dataset from the username UI, the credit card info is + * released to the application without the user knowledge. Similar, it's recommended to always + * protect a dataset that contains sensitive information by requiring dataset authentication + * (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}). + * + *

When the service detects that a screen have multiple partitions, it should return a + * {@link FillResponse} with just the datasets for the partition that originated the request (i.e., + * the partition that has the {@link android.app.assist.AssistStructure.ViewNode} whose + * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} returns {@code true}); then if + * the user selects a field from a different partition, the Android System will make another + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call for that partition, + * and so on. + * + *

Notice that when the user autofill a partition with the data provided by the service and the + * user did not change these fields, the autofilled value is sent back to the service in the + * subsequent calls (and can be obtained by calling + * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}). This is useful in the + * cases where the service must create datasets for a partition based on the choice made in a + * previous partition. For example, the 1st response for a screen that have credentials and address + * partitions could be: + * + *

+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ *         .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ *         .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ *         .build())
+ *     .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *         new AutofillId[] { id1, id2 })
+ *             .build())
+ *     .build();
+ * 
+ * + *

Then if the user selected {@code flanders}, the service would get a new + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call, with the values of + * the fields {@code id1} and {@code id2} prepopulated, so the service could then fetch the address + * for the Flanders account and return the following {@link FillResponse} for the address partition: + * + *

+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder() // partition 2 (address)
+ *         .setValue(id3, AutofillValue.forText("744 Evergreen Terrace"), createPresentation("744 Evergreen Terrace")) // street
+ *         .setValue(id4, AutofillValue.forText("Springfield"), createPresentation("Springfield")) // city
+ *         .build())
+ *     .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD | SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ *         new AutofillId[] { id1, id2 }) // username and password
+ *              .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ *             .build())
+ *     .build();
+ * 
+ * + *

When the service returns multiple {@link FillResponse}, the last one overrides the previous; + * that's why the {@link SaveInfo} in the 2nd request above has the info for both partitions. + * + *

Ignoring views

+ * + *

If the service find views that cannot be autofilled (for example, a text field representing + * the response to a Captcha challenge), it should mark those views as ignored by + * calling {@link FillResponse.Builder#setIgnoredIds(AutofillId...)} so the system does not trigger + * a new {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} when these views are + * focused. */ public abstract class AutofillService extends Service { private static final String TAG = "AutofillService"; @@ -132,11 +338,6 @@ public abstract class AutofillService extends Service { private HandlerCaller mHandlerCaller; - /** - * {@inheritDoc} - * - * NOTE: if overridden, it must call {@code super.onCreate()}. - */ @CallSuper @Override public void onCreate() { @@ -162,8 +363,7 @@ public abstract class AutofillService extends Service { } /** - * Called by the Android system do decide if an {@link Activity} can be autofilled by the - * service. + * Called by the Android system do decide if a screen can be autofilled by the service. * *

Service must call one of the {@link FillCallback} methods (like * {@link FillCallback#onSuccess(FillResponse)} @@ -181,7 +381,7 @@ public abstract class AutofillService extends Service { @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); /** - * Called when user requests service to save the fields of an {@link Activity}. + * Called when user requests service to save the fields of a screen. * *

Service must call one of the {@link SaveCallback} methods (like * {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)}) @@ -226,7 +426,7 @@ public abstract class AutofillService extends Service { * @return The history or {@code null} if there are no events. */ @Nullable public final FillEventHistory getFillEventHistory() { - AutofillManager afm = getSystemService(AutofillManager.class); + final AutofillManager afm = getSystemService(AutofillManager.class); if (afm == null) { return null; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index af2eb34f87513..a2ec0993c2c90 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -31,17 +31,23 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; /** - * A set of data that can be used to autofill an {@link android.app.Activity}. + * A dataset object represents a group of key/value pairs used to autofill parts of a screen. * - *

It contains: + *

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} + * 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 + * 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. * - *

    - *
  1. A list of values for input fields. - *
  2. A presentation view to visualize. - *
  3. An optional intent to authenticate. - *
+ *

In a more sophisticated form, the dataset value can be protected until the user authenticates + * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}. * - * @see android.service.autofill.FillResponse for examples. + * @see android.service.autofill.AutofillService for more information and examples about the + * role of datasets in the autofill workflow. */ public final class Dataset implements Parcelable { @@ -113,7 +119,7 @@ public final class Dataset implements Parcelable { } /** - * A builder for {@link Dataset} objects. You must to provide at least + * A builder for {@link Dataset} objects. You must provide at least * one value for a field or set an authentication intent. */ public static final class Builder { @@ -175,9 +181,9 @@ 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. * - *

Note: Do not make the provided pending intent + *

NOTE: 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.

+ * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * @return This builder. @@ -191,7 +197,7 @@ public final class Dataset implements Parcelable { } /** - * Sets the id for the dataset. + * Sets the id for the dataset so its usage history can be retrieved later. * *

The id of the last selected dataset can be read from * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear @@ -214,13 +220,12 @@ 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 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. * @return This builder. - * @throws IllegalStateException if the builder was constructed without a presentation - * ({@link RemoteViews}). + * @throws IllegalStateException if the builder was constructed without a + * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { throwIfDestroyed(); @@ -232,7 +237,8 @@ public final class Dataset implements Parcelable { } /** - * Sets the value of a field, using a custom presentation to visualize it. + * Sets the value of a field, using a custom {@link RemoteViews presentation} to + * visualize it. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. @@ -272,10 +278,12 @@ public final class Dataset implements Parcelable { } /** - * Creates a new {@link Dataset} instance. You should not interact - * with this builder once this method is called. It is required - * that you specified at least one field. Also it is mandatory to - * provide a presentation view to visualize the data set in the UI. + * Creates a new {@link Dataset} instance. + * + *

You should not interact with this builder once this method is called. + * + *

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. * * @return The built dataset. */ diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java index f8a87516854d5..cda2f4a23c9a1 100644 --- a/core/java/android/service/autofill/FillContext.java +++ b/core/java/android/service/autofill/FillContext.java @@ -30,7 +30,6 @@ import android.util.ArrayMap; import android.util.SparseIntArray; import android.view.autofill.AutofillId; -import java.util.ArrayList; import java.util.LinkedList; /** diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index b1145ee38929d..fd6da05aa2371 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; +import android.view.View; import com.android.internal.util.Preconditions; @@ -32,7 +33,7 @@ import java.util.ArrayList; import java.util.List; /** - * This class represents a request to an {@link AutofillService autofill provider} + * This class represents a request to an autofill service * to interpret the screen and provide information to the system which views are * interesting for saving and what are the possible ways to fill the inputs on * the screen if applicable. @@ -40,8 +41,29 @@ import java.util.List; * @see AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback) */ public final class FillRequest implements Parcelable { + /** * Indicates autofill was explicitly requested by the user. + * + *

Users typically make an explicit request to autofill a screen in two situations: + *

+ * + *

This flag is particularly useful for the second case. For example, the service could offer + * a complex UI where the user can map which screen views belong to each user data, or it could + * offer a simpler UI where the user picks the data for just the view used to trigger the + * request (that would be the view whose + * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} method returns {@code true}). + * + *

An explicit autofill request is triggered when the + * {@link android.view.autofill.AutofillManager#requestAutofill(View)} or + * {@link android.view.autofill.AutofillManager#requestAutofill(View, int, android.graphics.Rect)} + * is called. For example, standard {@link android.widget.TextView} views that use + * an {@link android.widget.Editor} shows an {@code AUTOFILL} option in the overflow menu that + * triggers such request. */ public static final int FLAG_MANUAL_REQUEST = 0x1; @@ -79,14 +101,14 @@ public final class FillRequest implements Parcelable { } /** - * @return The unique id of this request. + * Gets the unique id of this request. */ public int getId() { return mId; } /** - * @return The flags associated with this request. + * Gets the flags associated with this request. * * @see #FLAG_MANUAL_REQUEST */ @@ -95,7 +117,7 @@ public final class FillRequest implements Parcelable { } /** - * @return The contexts associated with each previous fill request. + * Gets the contexts associated with each previous fill request. */ public @NonNull List getFillContexts() { return mContexts; @@ -104,10 +126,10 @@ public final class FillRequest implements Parcelable { /** * Gets the extra client state returned from the last {@link * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback) - * fill request}. - *

- * Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) - * save request} is made the client state is cleared. + * fill request}, so the service can use it for state management. + * + *

Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) + * save request} is made, the client state is cleared. * * @return The client state. */ diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index fcf18eb5130e6..e13fdf68c831a 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; @@ -36,100 +37,7 @@ import java.util.Arrays; * Response for a {@link * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. * - *

The response typically contains one or more {@link Dataset}s, each representing a set of - * fields that can be autofilled together, and the Android system displays a dataset picker UI - * affordance that the user must use before the {@link android.app.Activity} is filled with - * the dataset. - * - *

For example, for a login page with username/password where the user only has one account in - * the response could be: - * - *

- *  new FillResponse.Builder()
- *      .add(new Dataset.Builder(createPresentation())
- *          .setValue(id1, AutofillValue.forText("homer"))
- *          .setValue(id2, AutofillValue.forText("D'OH!"))
- *          .build())
- *      .build();
- * 
- * - *

If the user had 2 accounts, each with its own user-provided names, the response could be: - * - *

- *  new FillResponse.Builder()
- *      .add(new Dataset.Builder(createFirstPresentation())
- *          .setValue(id1, AutofillValue.forText("homer"))
- *          .setValue(id2, AutofillValue.forText("D'OH!"))
- *          .build())
- *      .add(new Dataset.Builder(createSecondPresentation())
- *          .setValue(id1, AutofillValue.forText("elbarto")
- *          .setValue(id2, AutofillValue.forText("cowabonga")
- *          .build())
- *      .build();
- * 
- * - * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo} - * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the - * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details - * - *

If the service has multiple {@link Dataset}s for different sections of the activity, - * for example, a user section for which there are two datasets followed by an address - * section for which there are two datasets for each user user, then it should "partition" - * the activity in sections and populate the response with just a subset of the data that would - * fulfill the first section (the name in our example); then once the user fills the first - * section and taps a field from the next section (the address in our example), the Android - * system would issue another request for that section, and so on. Note that if the user - * chooses to populate the first section with a service provided dataset, the subsequent request - * would contain the populated values so you don't try to provide suggestions for the first - * section but ony for the second one based on the context of what was already filled. For - * example, the first response could be: - * - *

- *  new FillResponse.Builder()
- *      .add(new Dataset.Builder(createFirstPresentation())
- *          .setValue(id1, AutofillValue.forText("Homer"))
- *          .setValue(id2, AutofillValue.forText("Simpson"))
- *          .build())
- *      .add(new Dataset.Builder(createSecondPresentation())
- *          .setValue(id1, AutofillValue.forText("Bart"))
- *          .setValue(id2, AutofillValue.forText("Simpson"))
- *          .build())
- *      .build();
- * 
- * - *

Then after the user picks the second dataset and taps the street field to - * trigger another autofill request, the second response could be: - * - *

- *  new FillResponse.Builder()
- *      .add(new Dataset.Builder(createThirdPresentation())
- *          .setValue(id3, AutofillValue.forText("742 Evergreen Terrace"))
- *          .setValue(id4, AutofillValue.forText("Springfield"))
- *          .build())
- *      .add(new Dataset.Builder(createFourthPresentation())
- *          .setValue(id3, AutofillValue.forText("Springfield Power Plant"))
- *          .setValue(id4, AutofillValue.forText("Springfield"))
- *          .build())
- *      .build();
- * 
- * - *

The service could require user authentication at the {@link FillResponse} or the - * {@link Dataset} level, prior to autofilling an activity - see - * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and - * {@link Dataset.Builder#setAuthentication(IntentSender)}. - * - *

It is recommended that you encrypt only the sensitive data but leave the labels unencrypted - * which would allow you to provide a dataset presentation views with labels and if the user - * chooses one of them challenge the user to authenticate. For example, if the user has a - * home and a work address the Home and Work labels could be stored unencrypted as they don't - * have any sensitive data while the address data is in an encrypted storage. If the user - * chooses Home, then the platform will start your authentication flow. If you encrypt all - * data and require auth at the response level the user will have to interact with the fill - * UI to trigger a request for the datasets (as they don't see the presentation views for the - * possible options) which will start your auth flow and after successfully authenticating - * the user will be presented with the Home and Work options to pick one. Hence, you have - * flexibility how to implement your auth while storing labels non-encrypted and data - * encrypted provides a better user experience. + *

See the main {@link AutofillService} documentation for more details and examples. */ public final class FillResponse implements Parcelable { @@ -221,7 +129,7 @@ public final class FillResponse implements Parcelable { private boolean mDestroyed; /** - * Requires a fill response authentication before autofilling the activity with + * Requires a fill response authentication before autofilling the screen with * any data set in this response. * *

This is typically useful when a user interaction is required to unlock their @@ -230,16 +138,16 @@ public final class FillResponse implements Parcelable { * auth on the data set level leading to a better user experience. Note that if you * use sensitive data as a label, for example an email address, then it should also * be encrypted. The provided {@link android.app.PendingIntent intent} must be an - * activity which implements your authentication flow. Also if you provide an auth + * {@link Activity} which implements your authentication flow. Also if you provide an auth * intent you also need to specify the presentation view to be shown in the fill UI * for the user to trigger your authentication flow. * *

When a user triggers autofill, the system launches the provided intent * whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE - * client state}. Once you complete your authentication flow you should set the activity - * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated - * {@link FillResponse response} by setting it to the {@link + * client state}. Once you complete your authentication flow you should set the + * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully + * populated {@link FillResponse response} by setting it to the {@link * AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. * For example, if you provided an empty {@link FillResponse resppnse} because the * user's data was locked and marked that the response needs an authentication then @@ -286,8 +194,8 @@ public final class FillResponse implements Parcelable { * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, * FillCallback)} requests. * - *

This is typically used when the service cannot autofill the view; for example, an - * {@code EditText} representing a captcha. + *

This is typically used when the service cannot autofill the view; for example, a + * text field representing the result of a Captcha challenge. */ public Builder setIgnoredIds(AutofillId...ids) { mIgnoredIds = ids; @@ -316,8 +224,6 @@ public final class FillResponse implements Parcelable { /** * Sets the {@link SaveInfo} associated with this response. * - *

See {@link FillResponse} for more info. - * * @return This builder. */ public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { @@ -335,7 +241,7 @@ public final class FillResponse implements Parcelable { * fill requests and the subsequent save request. * *

If this method is called on multiple {@link FillResponse} objects for the same - * activity, just the latest bundle is passed back to the service. + * screen, just the latest bundle is passed back to the service. * *

Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) * save request} is made the client state is cleared. @@ -350,9 +256,10 @@ public final class FillResponse implements Parcelable { } /** - * Builds a new {@link FillResponse} instance. You must provide at least - * one dataset or some savable ids or an authentication with a presentation - * view. + * Builds a new {@link FillResponse} instance. + * + *

You must provide at least one dataset or some savable ids or an authentication with a + * presentation view. * * @return A built response. */ diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 6ea7d5edb4968..95d393b0234c2 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; @@ -45,70 +46,91 @@ import java.util.Arrays; * two pieces of information: * *

    - *
  1. The type of user data that would be saved (like passoword or credit card info). + *
  2. The type(s) of user data (like password or credit card info) that would be saved. *
  3. The minimum set of views (represented by their {@link AutofillId}) that need to be changed * to trigger a save request. *
* - * Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: + *

Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: * *

- *  new FillResponse.Builder()
- *      .add(new Dataset.Builder(createPresentation())
- *          .setValue(id1, AutofillValue.forText("homer"))
- *          .setValue(id2, AutofillValue.forText("D'OH!"))
- *          .build())
- *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
- *                  .build())
- *      .build();
+ *   new FillResponse.Builder()
+ *       .addDataset(new Dataset.Builder()
+ *           .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
+ *           .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
+ *           .build())
+ *       .setSaveInfo(new SaveInfo.Builder(
+ *           SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *           new AutofillId[] { id1, id2 }).build())
+ *       .build();
  * 
* - * There might be cases where the {@link AutofillService} knows how to fill the - * {@link android.app.Activity}, but the user has no data for it. In that case, the - * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s: + *

The save type flags are used to display the appropriate strings in the save UI affordance. + * You can pass multiple values, but try to keep it short if possible. In the above example, just + * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough. + * + *

There might be cases where the {@link AutofillService} knows how to fill the screen, + * but the user has no data for it. In that case, the {@link FillResponse} should contain just the + * {@link SaveInfo}, but no {@link Dataset Datasets}: * *

- *  new FillResponse.Builder()
- *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
- *                  .build())
- *      .build();
+ *   new FillResponse.Builder()
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *           new AutofillId[] { id1, id2 }).build())
+ *       .build();
  * 
* *

There might be cases where the user data in the {@link AutofillService} is enough * to populate some fields but not all, and the service would still be interested on saving the - * other fields. In this scenario, the service could set the + * other fields. In that case, the service could set the * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well: * *

  *   new FillResponse.Builder()
- *       .add(new Dataset.Builder(createPresentation())
- *          .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"))  // street
- *          .setValue(id2, AutofillValue.forText("Springfield"))            // city
- *          .build())
- *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2})
- *                   .setOptionalIds(new int[] {id3, id4}) // state and zipcode
- *                   .build())
+ *       .addDataset(new Dataset.Builder()
+ *           .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
+ *               createPresentation("742 Evergreen Terrace")) // street
+ *           .setValue(id2, AutofillValue.forText("Springfield"),
+ *               createPresentation("Springfield")) // city
+ *           .build())
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ *           new AutofillId[] { id1, id2 }) // street and  city
+ *           .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ *           .build())
  *       .build();
  * 
* - * The - * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} - * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions - * below are met: + *

The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after + * any of the following events: + *

* - *
    + *

    But it is only triggered when all conditions below are met: + *

+ * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value + * presented in the view). + *
  • The user explicitly tapped the UI affordance asking to save data for autofill. + * + * + *

    The service can also customize some aspects of the save UI affordance: + *

    */ public final class SaveInfo implements Parcelable { /** - * Type used on when the service can save the contents of an activity, but cannot describe what + * Type used when the service can save the contents of a screen, but cannot describe what * the content is for. */ public static final int SAVE_DATA_TYPE_GENERIC = 0x0; @@ -181,8 +203,8 @@ public final class SaveInfo implements Parcelable { /** * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} - * is called once the activity finishes. If this flag is set it is called once all saved views - * become invisible. + * is called once the {@link Activity} finishes. If this flag is set it is called once all + * saved views become invisible. */ public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1; @@ -294,9 +316,9 @@ public final class SaveInfo implements Parcelable { } /** - * Set flags changing the save behavior. + * Sets flags changing the save behavior. * - * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0. + * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}. * @return This builder. */ public @NonNull Builder setFlags(@SaveInfoFlags int flags) {