/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.autofill;

import static android.service.autofill.AutofillServiceHelper.assertValid;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.Helper.sDebug;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;

import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

Response for an AutofillService.onFillRequest(FillRequest, CancellationSignal, FillCallback).

See the main AutofillService documentation for more details and examples.

/** * Response for an {@link * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. * * <p>See the main {@link AutofillService} documentation for more details and examples. */
public final class FillResponse implements Parcelable {
Flag used to generate events of type Event.TYPE_CONTEXT_COMMITTED—if this flag is not passed to Builder.setFlags(int), these events are not generated.
/** * Flag used to generate {@link FillEventHistory.Event events} of type * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}&mdash;if this flag is not passed to * {@link Builder#setFlags(int)}, these events are not generated. */
public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
Flag used to change the behavior of Builder.disableAutofill(long)— when this flag is passed to Builder.setFlags(int), autofill is disabled only for the activiy that generated the FillRequest, not the whole app.
/** * Flag used to change the behavior of {@link FillResponse.Builder#disableAutofill(long)}&mdash; * when this flag is passed to {@link Builder#setFlags(int)}, autofill is disabled only for the * activiy that generated the {@link FillRequest}, not the whole app. */
public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
@hide
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_TRACK_CONTEXT_COMMITED, FLAG_DISABLE_ACTIVITY_ONLY }) @Retention(RetentionPolicy.SOURCE) @interface FillResponseFlags {} private final @Nullable ParceledListSlice<Dataset> mDatasets; private final @Nullable SaveInfo mSaveInfo; private final @Nullable Bundle mClientState; private final @Nullable RemoteViews mPresentation; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; private final long mDisableDuration; private final @Nullable AutofillId[] mFieldClassificationIds; private final int mFlags; private int mRequestId; private FillResponse(@NonNull Builder builder) { mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; mSaveInfo = builder.mSaveInfo; mClientState = builder.mClientState; mPresentation = builder.mPresentation; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; mDisableDuration = builder.mDisableDuration; mFieldClassificationIds = builder.mFieldClassificationIds; mFlags = builder.mFlags; mRequestId = INVALID_REQUEST_ID; }
@hide
/** @hide */
public @Nullable Bundle getClientState() { return mClientState; }
@hide
/** @hide */
public @Nullable List<Dataset> getDatasets() { return (mDatasets != null) ? mDatasets.getList() : null; }
@hide
/** @hide */
public @Nullable SaveInfo getSaveInfo() { return mSaveInfo; }
@hide
/** @hide */
public @Nullable RemoteViews getPresentation() { return mPresentation; }
@hide
/** @hide */
public @Nullable RemoteViews getHeader() { return mHeader; }
@hide
/** @hide */
public @Nullable RemoteViews getFooter() { return mFooter; }
@hide
/** @hide */
public @Nullable IntentSender getAuthentication() { return mAuthentication; }
@hide
/** @hide */
public @Nullable AutofillId[] getAuthenticationIds() { return mAuthenticationIds; }
@hide
/** @hide */
public @Nullable AutofillId[] getIgnoredIds() { return mIgnoredIds; }
@hide
/** @hide */
public long getDisableDuration() { return mDisableDuration; }
@hide
/** @hide */
public @Nullable AutofillId[] getFieldClassificationIds() { return mFieldClassificationIds; }
@hide
/** @hide */
@TestApi public int getFlags() { return mFlags; }
Associates a FillResponse to a request.

Set inside of the FillCallback code, not the AutofillService.

Params:
  • requestId – The id of the request to associate the response to.
@hide
/** * Associates a {@link FillResponse} to a request. * * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}. * * @param requestId The id of the request to associate the response to. * * @hide */
public void setRequestId(int requestId) { mRequestId = requestId; }
@hide
/** @hide */
public int getRequestId() { return mRequestId; }
Builder for FillResponse objects. You must to provide at least one dataset or set an authentication intent with a presentation view.
/** * Builder for {@link FillResponse} objects. You must to provide at least * one dataset or set an authentication intent with a presentation view. */
public static final class Builder { private ArrayList<Dataset> mDatasets; private SaveInfo mSaveInfo; private Bundle mClientState; private RemoteViews mPresentation; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; private long mDisableDuration; private AutofillId[] mFieldClassificationIds; private int mFlags; private boolean mDestroyed;
Triggers a custom UI before before autofilling the screen with any data set in this response.

Note: Although the name of this method suggests that it should be used just for authentication flow, it can be used for other advanced flows; see AutofillService for examples.

This is typically useful when a user interaction is required to unlock their data vault if you encrypt the data set labels and data set data. It is recommended to encrypt only the sensitive data and not the data set labels which would allow 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 intent must be an 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 screen content and your client state. Once you complete your authentication flow you should set the Activity result to Activity.RESULT_OK and set the AutofillManager.EXTRA_AUTHENTICATION_RESULT extra with the fully populated response (or null if the screen cannot be autofilled).

For example, if you provided an empty response because the user's data was locked and marked that the response needs an authentication then in the response returned if authentication succeeds you need to provide all available data sets some of which may need to be further authenticated, for example a credit card whose CVV needs to be entered.

If you provide an authentication intent you must also provide a presentation which is used to visualize visualize the response for triggering the authentication flow.

Note: Do not make the provided pending intent immutable by using PendingIntent.FLAG_IMMUTABLE as the platform needs to fill in the authentication arguments.

Theme does not work with RemoteViews layout. Avoid hardcoded text color or background color: Autofill on different platforms may have different themes.

Params:
  • authentication – Intent to an activity with your authentication flow.
  • presentation – The presentation to visualize the response.
  • ids – id of Views that when focused will display the authentication UI.
Throws:
  • IllegalArgumentException – if any of the following occurs:
    • ids is null
    • ids is empty
    • ids contains a null element
    • both authentication and presentation are null
    • both authentication and presentation are non-null
  • IllegalStateException – if a header or a footer are already set for this builder.
See Also:
Returns:This builder.
/** * Triggers a custom UI before before autofilling the screen with any data set in this * response. * * <p><b>Note:</b> Although the name of this method suggests that it should be used just for * authentication flow, it can be used for other advanced flows; see {@link AutofillService} * for examples. * * <p>This is typically useful when a user interaction is required to unlock their * data vault if you encrypt the data set labels and data set data. It is recommended * to encrypt only the sensitive data and not the data set labels which would allow * 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 * {@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 android.view.autofill.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 * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra * with the fully populated {@link FillResponse response} (or {@code null} if the screen * cannot be autofilled). * * <p>For example, if you provided an empty {@link FillResponse response} because the * user's data was locked and marked that the response needs an authentication then * in the response returned if authentication succeeds you need to provide all * available data sets some of which may need to be further authenticated, for * example a credit card whose CVV needs to be entered. * * <p>If you provide an authentication intent you must also provide a presentation * which is used to visualize visualize the response for triggering the authentication * flow. * * <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>Theme does not work with RemoteViews layout. Avoid hardcoded text color * or background color: Autofill on different platforms may have different themes. * * @param authentication Intent to an activity with your authentication flow. * @param presentation The presentation to visualize the response. * @param ids id of Views that when focused will display the authentication UI. * * @return This builder. * * @throws IllegalArgumentException if any of the following occurs: * <ul> * <li>{@code ids} is {@code null}</li> * <li>{@code ids} is empty</li> * <li>{@code ids} contains a {@code null} element</li> * <li>both {@code authentication} and {@code presentation} are {@code null}</li> * <li>both {@code authentication} and {@code presentation} are non-{@code null}</li> * </ul> * * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a * {@link #setFooter(RemoteViews) footer} are already set for this builder. * * @see android.app.PendingIntent#getIntentSender() */
public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { throw new IllegalStateException("Already called #setHeader() or #setFooter()"); } if (authentication == null ^ presentation == null) { throw new IllegalArgumentException("authentication and presentation" + " must be both non-null or null"); } mAuthentication = authentication; mPresentation = presentation; mAuthenticationIds = assertValid(ids); return this; }
Specifies views that should not trigger new AutofillService.onFillRequest(FillRequest, CancellationSignal, FillCallback) requests.

This is typically used when the service cannot autofill the view; for example, a text field representing the result of a Captcha challenge.

/** * Specifies views that should not trigger new * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, * FillCallback)} requests. * * <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) { throwIfDestroyed(); mIgnoredIds = ids; return this; }
Adds a new Dataset to this response.

Note: on Android VERSION_CODES.O, the total number of datasets is limited by the Binder transaction size, so it's recommended to keep it small (in the range of 10-20 at most) and use pagination by adding a fake authenticated dataset at the end with a presentation string like "Next 10" that would return a new FillResponse with the next 10 datasets, and so on. This limitation was lifted on Android VERSION_CODES.O_MR1, although the Binder transaction size can still be reached if each dataset itself is too big.

Returns:This builder.
/** * Adds a new {@link Dataset} to this response. * * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of * datasets is limited by the Binder transaction size, so it's recommended to keep it * small (in the range of 10-20 at most) and use pagination by adding a fake * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end * with a presentation string like "Next 10" that would return a new {@link FillResponse} * with the next 10 datasets, and so on. This limitation was lifted on * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction * size can still be reached if each dataset itself is too big. * * @return This builder. */
public @NonNull Builder addDataset(@Nullable Dataset dataset) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (dataset == null) { return this; } if (mDatasets == null) { mDatasets = new ArrayList<>(); } if (!mDatasets.add(dataset)) { return this; } return this; }
Sets the SaveInfo associated with this response.
Returns:This builder.
/** * Sets the {@link SaveInfo} associated with this response. * * @return This builder. */
public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { throwIfDestroyed(); throwIfDisableAutofillCalled(); mSaveInfo = saveInfo; return this; }
Sets a bundle with state that is passed to subsequent APIs that manipulate this response.

You can use this bundle to store intermediate state that is passed to subsequent calls to AutofillService.onFillRequest(FillRequest, CancellationSignal, FillCallback) and AutofillService.onSaveRequest(SaveRequest, SaveCallback), and you can also retrieve it by calling Event.getClientState().

If this method is called on multiple FillResponse objects for the same screen, just the latest bundle is passed back to the service.

Params:
  • clientState – The custom client state.
Returns:This builder.
/** * Sets a bundle with state that is passed to subsequent APIs that manipulate this response. * * <p>You can use this bundle to store intermediate state that is passed to subsequent calls * to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}, and * you can also retrieve it by calling {@link FillEventHistory.Event#getClientState()}. * * <p>If this method is called on multiple {@link FillResponse} objects for the same * screen, just the latest bundle is passed back to the service. * * @param clientState The custom client state. * @return This builder. */
public Builder setClientState(@Nullable Bundle clientState) { throwIfDestroyed(); throwIfDisableAutofillCalled(); mClientState = clientState; return this; }
Sets which fields are used for field classification

Note: This method automatically adds the FillResponse.FLAG_TRACK_CONTEXT_COMMITED to the flags.

Throws:
/** * Sets which fields are used for * <a href="AutofillService.html#FieldClassification">field classification</a> * * <p><b>Note:</b> This method automatically adds the * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} to the {@link #setFlags(int) flags}. * @throws IllegalArgumentException is length of {@code ids} args is more than * {@link UserData#getMaxFieldClassificationIdsSize()}. * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was * already called. * @throws NullPointerException if {@code ids} or any element on it is {@code null}. */
public Builder setFieldClassificationIds(@NonNull AutofillId... ids) { throwIfDestroyed(); throwIfDisableAutofillCalled(); Preconditions.checkArrayElementsNotNull(ids, "ids"); Preconditions.checkArgumentInRange(ids.length, 1, UserData.getMaxFieldClassificationIdsSize(), "ids length"); mFieldClassificationIds = ids; mFlags |= FLAG_TRACK_CONTEXT_COMMITED; return this; }
Sets flags changing the response behavior.
Params:
Returns:This builder.
/** * Sets flags changing the response behavior. * * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. * * @return This builder. */
public Builder setFlags(@FillResponseFlags int flags) { throwIfDestroyed(); mFlags = Preconditions.checkFlagsArgument(flags, FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); return this; }
Disables autofill for the app or activity.

This method is useful to optimize performance in cases where the service knows it can not autofill an app—for example, when the service has a list of "blacklisted" apps such as office suites.

By default, it disables autofill for all activities in the app, unless the response is flagged with FillResponse.FLAG_DISABLE_ACTIVITY_ONLY.

Autofill for the app or activity is automatically re-enabled after any of the following conditions:

  1. duration milliseconds have passed.
  2. The autofill service for the user has changed.
  3. The device has rebooted.

Note: Activities that are running when autofill is re-enabled remain disabled for autofill until they finish and restart.

Params:
  • duration – duration to disable autofill, in milliseconds.
Throws:
Returns:this builder
/** * Disables autofill for the app or activity. * * <p>This method is useful to optimize performance in cases where the service knows it * can not autofill an app&mdash;for example, when the service has a list of "blacklisted" * apps such as office suites. * * <p>By default, it disables autofill for all activities in the app, unless the response is * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}. * * <p>Autofill for the app or activity is automatically re-enabled after any of the * following conditions: * * <ol> * <li>{@code duration} milliseconds have passed. * <li>The autofill service for the user has changed. * <li>The device has rebooted. * </ol> * * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain * disabled for autofill until they finish and restart. * * @param duration duration to disable autofill, in milliseconds. * * @return this builder * * @throws IllegalArgumentException if {@code duration} is not a positive number. * @throws IllegalStateException if either {@link #addDataset(Dataset)}, * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or * {@link #setFieldClassificationIds(AutofillId...)} was already called. */
public Builder disableAutofill(long duration) { throwIfDestroyed(); if (duration <= 0) { throw new IllegalArgumentException("duration must be greater than 0"); } if (mAuthentication != null || mDatasets != null || mSaveInfo != null || mFieldClassificationIds != null || mClientState != null) { throw new IllegalStateException("disableAutofill() must be the only method called"); } mDisableDuration = duration; return this; }
Sets a header to be shown as the first element in the list of datasets.

When this method is called, you must also add a dataset, otherwise build() throws an IllegalStateException. Similarly, this method should only be used on FillResponses that do not require authentication (as the header could have been set directly in the main presentation in these cases).

Theme does not work with RemoteViews layout. Avoid hardcoded text color or background color: Autofill on different platforms may have different themes.

Params:
Throws:
Returns:this builder
/** * Sets a header to be shown as the first element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the header could have been set directly in the main presentation in * these cases). * * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color * or background color: Autofill on different platforms may have different themes. * * @param header a presentation to represent the header. This presentation is not clickable * &mdash;calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if an * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was * already set for this builder. */
// TODO(b/69796626): make it sticky / update javadoc public Builder setHeader(@NonNull RemoteViews header) { throwIfDestroyed(); throwIfAuthenticationCalled(); mHeader = Preconditions.checkNotNull(header); return this; }
Sets a footer to be shown as the last element in the list of datasets.

When this method is called, you must also add a dataset, otherwise build() throws an IllegalStateException. Similarly, this method should only be used on FillResponses that do not require authentication (as the footer could have been set directly in the main presentation in these cases).

Theme does not work with RemoteViews layout. Avoid hardcoded text color or background color: Autofill on different platforms may have different themes.

Params:
Throws:
Returns:this builder
/** * Sets a footer to be shown as the last element in the list of datasets. * * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this * method should only be used on {@link FillResponse FillResponses} that do not require * authentication (as the footer could have been set directly in the main presentation in * these cases). * * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color * or background color: Autofill on different platforms may have different themes. * * @param footer a presentation to represent the footer. This presentation is not clickable * &mdash;calling * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would * have no effect. * * @return this builder * * @throws IllegalStateException if the FillResponse * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) * requires authentication}. */
// TODO(b/69796626): make it sticky / update javadoc public Builder setFooter(@NonNull RemoteViews footer) { throwIfDestroyed(); throwIfAuthenticationCalled(); mFooter = Preconditions.checkNotNull(footer); return this; }
Builds a new FillResponse instance.
Throws:
Returns:A built response.
/** * Builds a new {@link FillResponse} instance. * * @throws IllegalStateException if any of the following conditions occur: * <ol> * <li>{@link #build()} was already called. * <li>No call was made to {@link #addDataset(Dataset)}, * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, * {@link #setClientState(Bundle)}, * or {@link #setFieldClassificationIds(AutofillId...)}. * <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called * without any previous calls to {@link #addDataset(Dataset)}. * </ol> * * @return A built response. */
public FillResponse build() { throwIfDestroyed(); if (mAuthentication == null && mDatasets == null && mSaveInfo == null && mDisableDuration == 0 && mFieldClassificationIds == null && mClientState == null) { throw new IllegalStateException("need to provide: at least one DataSet, or a " + "SaveInfo, or an authentication with a presentation, " + "or a FieldsDetection, or a client state, or disable autofill"); } if (mDatasets == null && (mHeader != null || mFooter != null)) { throw new IllegalStateException( "must add at least 1 dataset when using header or footer"); } mDestroyed = true; return new FillResponse(this); } private void throwIfDestroyed() { if (mDestroyed) { throw new IllegalStateException("Already called #build()"); } } private void throwIfDisableAutofillCalled() { if (mDisableDuration > 0) { throw new IllegalStateException("Already called #disableAutofill()"); } } private void throwIfAuthenticationCalled() { if (mAuthentication != null) { throw new IllegalStateException("Already called #setAuthentication()"); } } } ///////////////////////////////////// // Object "contract" methods. // ///////////////////////////////////// @Override public String toString() { if (!sDebug) return super.toString(); // TODO: create a dump() method instead final StringBuilder builder = new StringBuilder( "FillResponse : [mRequestId=" + mRequestId); if (mDatasets != null) { builder.append(", datasets=").append(mDatasets.getList()); } if (mSaveInfo != null) { builder.append(", saveInfo=").append(mSaveInfo); } if (mClientState != null) { builder.append(", hasClientState"); } if (mPresentation != null) { builder.append(", hasPresentation"); } if (mHeader != null) { builder.append(", hasHeader"); } if (mFooter != null) { builder.append(", hasFooter"); } if (mAuthentication != null) { builder.append(", hasAuthentication"); } if (mAuthenticationIds != null) { builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)); } builder.append(", disableDuration=").append(mDisableDuration); if (mFlags != 0) { builder.append(", flags=").append(mFlags); } if (mFieldClassificationIds != null) { builder.append(Arrays.toString(mFieldClassificationIds)); } return builder.append("]").toString(); } ///////////////////////////////////// // Parcelable "contract" methods. // ///////////////////////////////////// @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mDatasets, flags); parcel.writeParcelable(mSaveInfo, flags); parcel.writeParcelable(mClientState, flags); parcel.writeParcelableArray(mAuthenticationIds, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); parcel.writeParcelableArray(mFieldClassificationIds, flags); parcel.writeInt(mFlags); parcel.writeInt(mRequestId); } public static final Parcelable.Creator<FillResponse> CREATOR = new Parcelable.Creator<FillResponse>() { @Override public FillResponse createFromParcel(Parcel parcel) { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. final Builder builder = new Builder(); final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null); final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null; final int datasetCount = (datasets != null) ? datasets.size() : 0; for (int i = 0; i < datasetCount; i++) { builder.addDataset(datasets.get(i)); } builder.setSaveInfo(parcel.readParcelable(null)); builder.setClientState(parcel.readParcelable(null)); // Sets authentication state. final AutofillId[] authenticationIds = parcel.readParcelableArray(null, AutofillId.class); final IntentSender authentication = parcel.readParcelable(null); final RemoteViews presentation = parcel.readParcelable(null); if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation); } final RemoteViews header = parcel.readParcelable(null); if (header != null) { builder.setHeader(header); } final RemoteViews footer = parcel.readParcelable(null); if (footer != null) { builder.setFooter(footer); } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); final long disableDuration = parcel.readLong(); if (disableDuration > 0) { builder.disableAutofill(disableDuration); } final AutofillId[] fieldClassifactionIds = parcel.readParcelableArray(null, AutofillId.class); if (fieldClassifactionIds != null) { builder.setFieldClassificationIds(fieldClassifactionIds); } builder.setFlags(parcel.readInt()); final FillResponse response = builder.build(); response.setRequestId(parcel.readInt()); return response; } @Override public FillResponse[] newArray(int size) { return new FillResponse[size]; } }; }