diff options
author | Felipe Leme <felipeal@google.com> | 2017-06-05 11:32:32 -0700 |
---|---|---|
committer | Felipe Leme <felipeal@google.com> | 2017-06-22 15:25:31 -0700 |
commit | 2ef19c1d73f89ca4718b5a8f0c2e7221621e844f (patch) | |
tree | bbff9024392396a97a2da3dd07950bcfd3f2385b | |
parent | 16503a8f7e983178356f0c4bbbdaf71037e13a40 (diff) | |
download | base-2ef19c1d73f89ca4718b5a8f0c2e7221621e844f.tar.gz |
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
-rw-r--r-- | core/java/android/service/autofill/AutofillService.java | 228 | ||||
-rw-r--r-- | core/java/android/service/autofill/Dataset.java | 50 | ||||
-rw-r--r-- | core/java/android/service/autofill/FillContext.java | 1 | ||||
-rw-r--r-- | core/java/android/service/autofill/FillRequest.java | 38 | ||||
-rw-r--r-- | core/java/android/service/autofill/FillResponse.java | 121 | ||||
-rw-r--r-- | core/java/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 394bd0ac70f7..a80ef032e68f 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 + * <a href="{@docRoot}preview/features/autofill.html">Autofill Framework</a>. + * + * <p>An {@code AutofillService} is only bound to the Android System for autofill purposes if: + * <ol> + * <li>It requires the {@code android.permission.BIND_AUTOFILL_SERVICE} permission in its + * manifest. + * <li>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). + * </ol> + * + * <h3>Basic usage</h3> + * + * <p>The basic autofill process is defined by the workflow below: + * <ol> + * <li>User focus an editable {@link View}. + * <li>View calls {@link AutofillManager#notifyViewEntered(android.view.View)}. + * <li>A {@link ViewStructure} representing all views in the screen is created. + * <li>The Android System binds to the service and calls {@link #onConnected()}. + * <li>The service receives the view structure through the + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)}. + * <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}. + * <li>The Android System calls {@link #onDisconnected()} and unbinds from the + * {@code AutofillService}. + * <li>The Android System displays an UI affordance with the options sent by the service. + * <li>The user picks an option. + * <li>The proper views are autofilled. + * </ol> + * + * <p>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). + * + * <p>Typically, the + * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will: + * <ol> + * <li>Parse the view structure looking for autofillable views (for example, using + * {@link android.app.assist.AssistStructure.ViewNode#getAutofillHints()}. + * <li>Match the autofillable views with the user's data. + * <li>Create a {@link Dataset} for each set of user's data that match those fields. + * <li>Fill the dataset(s) with the proper {@link AutofillId}s and {@link AutofillValue}s. + * <li>Add the dataset(s) to the {@link FillResponse} passed to + * {@link FillCallback#onSuccess(FillResponse)}. + * </ol> + * + * <p>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: + * + * <pre class="prettyprint"> + * 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(); + * </pre> + * + * <p>But if the user had 2 accounts instead, the response could be: + * + * <pre class="prettyprint"> + * 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(); + * </pre> + * + * <p>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. + * + * <h3>Saving user data</h3> + * + * <p>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. + * + * <h3>User authentication</h3> + * + * <p>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: + * <ul> + * <li>To unlock the user data (for example, using a master password or fingerprint + * authentication) - see + * {@link FillResponse.Builder#setAuthentication(AutofillId[], android.content.IntentSender, android.widget.RemoteViews)}. + * <li>To unlock a specific dataset (for example, by providing a CVC for a credit card) - see + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}. + * </ul> + * + * <p>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. * - * <p>Apps providing autofill capabilities must extend this service. + * <p>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: + * + * <ol> + * <li>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". + * <li>When the user selects the fake dataset, the service displays a dialog with available + * banking apps. + * <li>When the user select a banking app, the service replies with a new {@link FillResponse} + * containing the datasets for that bank. + * </ol> + * + * <p>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. + * + * <h3>Data partitioning</h3> + * + * <p>The autofillable views in a screen should be grouped in logical groups called "partitions". + * Typical partitions are: + * <ul> + * <li>Credentials (username/email address, password). + * <li>Address (street, city, state, zip code, etc). + * <li>Payment info (credit card number, expiration date, and verification code). + * </ul> + * <p>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)}). + * + * <p>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. + * + * <p>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: + * + * <pre class="prettyprint"> + * 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(); + * </pre> + * + * <p>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: + * + * <pre class="prettyprint"> + * 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(); + * </pre> + * + * <p>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. + * + * <h3>Ignoring views</h3> + * + * <p>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} - * - * <strong>NOTE: </strong>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. * * <p>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. * * <p>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 af2eb34f8751..a2ec0993c2c9 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. * - * <p>It contains: + * <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} + * 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. * - * <ol> - * <li>A list of values for input fields. - * <li>A presentation view to visualize. - * <li>An optional intent to authenticate. - * </ol> + * <p>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. * - * <p></><strong>Note:</strong> 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.</p> + * 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. * * <p>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. + * + * <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. * * @return The built dataset. */ diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java index f8a87516854d..cda2f4a23c9a 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 b1145ee38929..fd6da05aa237 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. + * + * <p>Users typically make an explicit request to autofill a screen in two situations: + * <ul> + * <li>The app disabled autofill (using {@link View#setImportantForAutofill(int)}. + * <li>The service could not figure out how to autofill a screen (but the user knows the + * service has data for that app). + * </ul> + * + * <p>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}). + * + * <p>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<FillContext> 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}. - * <p> - * 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. + * + * <p>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 fcf18eb5130e..e13fdf68c831 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)}. * - * <p>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. - * - * <p>For example, for a login page with username/password where the user only has one account in - * the response could be: - * - * <pre class="prettyprint"> - * new FillResponse.Builder() - * .add(new Dataset.Builder(createPresentation()) - * .setValue(id1, AutofillValue.forText("homer")) - * .setValue(id2, AutofillValue.forText("D'OH!")) - * .build()) - * .build(); - * </pre> - * - * <p>If the user had 2 accounts, each with its own user-provided names, the response could be: - * - * <pre class="prettyprint"> - * 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(); - * </pre> - * - * 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 - * - * <p>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: - * - * <pre class="prettyprint"> - * 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(); - * </pre> - * - * <p>Then after the user picks the second dataset and taps the street field to - * trigger another autofill request, the second response could be: - * - * <pre class="prettyprint"> - * 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(); - * </pre> - * - * <p>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)}. - * - * <p>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. + * <p>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. * * <p>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. * * <p>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. * - * <p>This is typically used when the service cannot autofill the view; for example, an - * {@code EditText} representing a captcha. + * <p>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. * - * <p>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. * * <p>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. * * <p>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. + * + * <p>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 6ea7d5edb496..95d393b0234c 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: * * <ol> - * <li>The type of user data that would be saved (like passoword or credit card info). + * <li>The type(s) of user data (like password or credit card info) that would be saved. * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed * to trigger a save request. * </ol> * - * Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: + * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: * * <pre class="prettyprint"> - * 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(); * </pre> * - * 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: + * <p>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. + * + * <p>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}: * * <pre class="prettyprint"> - * 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(); * </pre> * * <p>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: * * <pre class="prettyprint"> * 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(); * </pre> * - * The - * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} - * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions - * below are met: + * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after + * any of the following events: + * <ul> + * <li>The {@link Activity} finishes. + * <li>The app explicitly called {@link AutofillManager#commit()}. + * <li>All required views became invisible (if the {@link SaveInfo} was created with the + * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag). + * </ul> * - * <ol> + * <p>But it is only triggered when all conditions below are met: + * <ul> * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}. - * <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed - * to {@link SaveInfo.Builder} constructor are not empty. + * <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed + * to the {@link SaveInfo.Builder} constructor are not empty. * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed - * (i.e., it's not the same value passed in a {@link Dataset}). - * <li>The user explicitly tapped the affordance asking to save data for autofill. - * </ol> + * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value + * presented in the view). + * <li>The user explicitly tapped the UI affordance asking to save data for autofill. + * </ul> + * + * <p>The service can also customize some aspects of the save UI affordance: + * <ul> + * <li>Add a subtitle by calling {@link Builder#setDescription(CharSequence)}. + * <li>Customize the button used to reject the save request by calling + * {@link Builder#setNegativeAction(int, IntentSender)}. + * </ul> */ 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) { |