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: + *
The basic autofill process is defined by the workflow below: + *
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: + *
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. + * + *
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. + * + *
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: + * + *
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. + * + *
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. + * + *
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. * - *
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
- * 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:
- *
- * If the user had 2 accounts, each with its own user-provided names, the response could be:
- *
- * 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:
- *
- * Then after the user picks the second dataset and taps the street field to
- * trigger another autofill request, the second response could be:
- *
- * 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:
*
* Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
*
* 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}:
*
* 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:
*
* 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:
+ * The service can also customize some aspects of the save UI affordance:
+ *
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createPresentation())
- * .setValue(id1, AutofillValue.forText("homer"))
- * .setValue(id2, AutofillValue.forText("D'OH!"))
- * .build())
- * .build();
- *
- *
- *
- * 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
- *
- *
- * 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();
- *
- *
- *
- * 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();
- *
- *
- *
- *
*
- * 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:
+ *
- * 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();
*
*
*
* 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:
+ *
+ *
*
- *
+ *
+ * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
+ * presented in the view).
+ *
*
+ *
*/
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) {