| /* |
| * 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.DrawableRes; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.StringRes; |
| import android.annotation.SuppressLint; |
| import android.annotation.TestApi; |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.content.pm.ParceledListSlice; |
| import android.os.Bundle; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.service.assist.classification.FieldClassification; |
| 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; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * 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 { |
| // common_typos_disable |
| |
| /** |
| * Flag used to generate {@link FillEventHistory.Event events} of type |
| * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}—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 {@link FillResponse.Builder#disableAutofill(long)}— |
| * 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; |
| |
| /** |
| * Flag used to request to wait for a delayed fill from the remote Autofill service if it's |
| * passed to {@link Builder#setFlags(int)}. |
| * |
| * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send |
| * a {@link FillResponse} to the latest {@link FillRequest} via |
| * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback} |
| * has timed out. |
| */ |
| public static final int FLAG_DELAY_FILL = 0x4; |
| |
| /** |
| * @hide |
| */ |
| public static final int FLAG_CREDENTIAL_MANAGER_RESPONSE = 0x8; |
| |
| /** @hide */ |
| @IntDef(flag = true, prefix = { "FLAG_" }, value = { |
| FLAG_TRACK_CONTEXT_COMMITED, |
| FLAG_DISABLE_ACTIVITY_ONLY, |
| FLAG_DELAY_FILL, |
| FLAG_CREDENTIAL_MANAGER_RESPONSE |
| }) |
| @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 InlinePresentation mInlinePresentation; |
| private final @Nullable InlinePresentation mInlineTooltipPresentation; |
| private final @Nullable RemoteViews mDialogPresentation; |
| private final @Nullable RemoteViews mDialogHeader; |
| 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 @Nullable AutofillId[] mFillDialogTriggerIds; |
| private final long mDisableDuration; |
| private final @Nullable AutofillId[] mFieldClassificationIds; |
| private final int mFlags; |
| private int mRequestId; |
| private final @Nullable UserData mUserData; |
| private final @Nullable int[] mCancelIds; |
| private final boolean mSupportsInlineSuggestions; |
| private final @DrawableRes int mIconResourceId; |
| private final @StringRes int mServiceDisplayNameResourceId; |
| private final boolean mShowFillDialogIcon; |
| private final boolean mShowSaveDialogIcon; |
| private final @Nullable FieldClassification[] mDetectedFieldTypes; |
| private final @Nullable PendingIntent mDialogPendingIntent; |
| |
| /** |
| * Creates a shollow copy of the provided FillResponse. |
| * |
| * @hide |
| */ |
| public static FillResponse shallowCopy( |
| FillResponse r, List<Dataset> datasets, SaveInfo saveInfo) { |
| return new FillResponse( |
| (datasets != null) ? new ParceledListSlice<>(datasets) : null, |
| saveInfo, |
| r.mClientState, |
| r.mPresentation, |
| r.mInlinePresentation, |
| r.mInlineTooltipPresentation, |
| r.mDialogPresentation, |
| r.mDialogHeader, |
| r.mHeader, |
| r.mFooter, |
| r.mAuthentication, |
| r.mAuthenticationIds, |
| r.mIgnoredIds, |
| r.mFillDialogTriggerIds, |
| r.mDisableDuration, |
| r.mFieldClassificationIds, |
| r.mFlags, |
| r.mRequestId, |
| r.mUserData, |
| r.mCancelIds, |
| r.mSupportsInlineSuggestions, |
| r.mIconResourceId, |
| r.mServiceDisplayNameResourceId, |
| r.mShowFillDialogIcon, |
| r.mShowSaveDialogIcon, |
| r.mDetectedFieldTypes, |
| r.mDialogPendingIntent); |
| } |
| |
| private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState, |
| RemoteViews presentation, InlinePresentation inlinePresentation, |
| InlinePresentation inlineTooltipPresentation, RemoteViews dialogPresentation, |
| RemoteViews dialogHeader, RemoteViews header, RemoteViews footer, |
| IntentSender authentication, AutofillId[] authenticationIds, AutofillId[] ignoredIds, |
| AutofillId[] fillDialogTriggerIds, long disableDuration, |
| AutofillId[] fieldClassificationIds, int flags, int requestId, UserData userData, |
| int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId, |
| int serviceDisplayNameResourceId, boolean showFillDialogIcon, |
| boolean showSaveDialogIcon, |
| FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) { |
| mDatasets = datasets; |
| mSaveInfo = saveInfo; |
| mClientState = clientState; |
| mPresentation = presentation; |
| mInlinePresentation = inlinePresentation; |
| mInlineTooltipPresentation = inlineTooltipPresentation; |
| mDialogPresentation = dialogPresentation; |
| mDialogHeader = dialogHeader; |
| mHeader = header; |
| mFooter = footer; |
| mAuthentication = authentication; |
| mAuthenticationIds = authenticationIds; |
| mIgnoredIds = ignoredIds; |
| mFillDialogTriggerIds = fillDialogTriggerIds; |
| mDisableDuration = disableDuration; |
| mFieldClassificationIds = fieldClassificationIds; |
| mFlags = flags; |
| mRequestId = requestId; |
| mUserData = userData; |
| mCancelIds = cancelIds; |
| mSupportsInlineSuggestions = supportsInlineSuggestions; |
| mIconResourceId = iconResourceId; |
| mServiceDisplayNameResourceId = serviceDisplayNameResourceId; |
| mShowFillDialogIcon = showFillDialogIcon; |
| mShowSaveDialogIcon = showSaveDialogIcon; |
| mDetectedFieldTypes = detectedFieldTypes; |
| mDialogPendingIntent = dialogPendingIntent; |
| } |
| |
| private FillResponse(@NonNull Builder builder) { |
| mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; |
| mSaveInfo = builder.mSaveInfo; |
| mClientState = builder.mClientState; |
| mPresentation = builder.mPresentation; |
| mInlinePresentation = builder.mInlinePresentation; |
| mInlineTooltipPresentation = builder.mInlineTooltipPresentation; |
| mDialogPresentation = builder.mDialogPresentation; |
| mDialogHeader = builder.mDialogHeader; |
| mHeader = builder.mHeader; |
| mFooter = builder.mFooter; |
| mAuthentication = builder.mAuthentication; |
| mAuthenticationIds = builder.mAuthenticationIds; |
| mFillDialogTriggerIds = builder.mFillDialogTriggerIds; |
| mIgnoredIds = builder.mIgnoredIds; |
| mDisableDuration = builder.mDisableDuration; |
| mFieldClassificationIds = builder.mFieldClassificationIds; |
| mFlags = builder.mFlags; |
| mRequestId = INVALID_REQUEST_ID; |
| mUserData = builder.mUserData; |
| mCancelIds = builder.mCancelIds; |
| mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions; |
| mIconResourceId = builder.mIconResourceId; |
| mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId; |
| mShowFillDialogIcon = builder.mShowFillDialogIcon; |
| mShowSaveDialogIcon = builder.mShowSaveDialogIcon; |
| mDetectedFieldTypes = builder.mDetectedFieldTypes; |
| mDialogPendingIntent = builder.mDialogPendingIntent; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Set<FieldClassification> getDetectedFieldClassifications() { |
| return Set.of(mDetectedFieldTypes); |
| } |
| |
| /** @hide */ |
| public @Nullable Bundle getClientState() { |
| return mClientState; |
| } |
| |
| /** @hide */ |
| public @Nullable List<Dataset> getDatasets() { |
| return (mDatasets != null) ? mDatasets.getList() : null; |
| } |
| |
| /** @hide */ |
| public @Nullable SaveInfo getSaveInfo() { |
| return mSaveInfo; |
| } |
| |
| /** @hide */ |
| public @Nullable RemoteViews getPresentation() { |
| return mPresentation; |
| } |
| |
| /** @hide */ |
| public @Nullable InlinePresentation getInlinePresentation() { |
| return mInlinePresentation; |
| } |
| |
| /** @hide */ |
| public @Nullable InlinePresentation getInlineTooltipPresentation() { |
| return mInlineTooltipPresentation; |
| } |
| |
| /** @hide */ |
| public @Nullable RemoteViews getDialogPresentation() { |
| return mDialogPresentation; |
| } |
| |
| /** @hide */ |
| public @Nullable RemoteViews getDialogHeader() { |
| return mDialogHeader; |
| } |
| |
| /** @hide */ |
| public @Nullable RemoteViews getHeader() { |
| return mHeader; |
| } |
| |
| /** @hide */ |
| public @Nullable RemoteViews getFooter() { |
| return mFooter; |
| } |
| |
| /** @hide */ |
| public @Nullable IntentSender getAuthentication() { |
| return mAuthentication; |
| } |
| |
| /** @hide */ |
| public @Nullable AutofillId[] getAuthenticationIds() { |
| return mAuthenticationIds; |
| } |
| |
| /** @hide */ |
| public @Nullable AutofillId[] getFillDialogTriggerIds() { |
| return mFillDialogTriggerIds; |
| } |
| |
| /** @hide */ |
| public @Nullable AutofillId[] getIgnoredIds() { |
| return mIgnoredIds; |
| } |
| |
| /** @hide */ |
| public long getDisableDuration() { |
| return mDisableDuration; |
| } |
| |
| /** @hide */ |
| public @Nullable AutofillId[] getFieldClassificationIds() { |
| return mFieldClassificationIds; |
| } |
| |
| /** @hide */ |
| public @Nullable UserData getUserData() { |
| return mUserData; |
| } |
| |
| /** @hide */ |
| public @DrawableRes int getIconResourceId() { |
| return mIconResourceId; |
| } |
| |
| /** @hide */ |
| public @StringRes int getServiceDisplayNameResourceId() { |
| return mServiceDisplayNameResourceId; |
| } |
| |
| /** @hide */ |
| public boolean getShowFillDialogIcon() { |
| return mShowFillDialogIcon; |
| } |
| |
| /** @hide */ |
| public boolean getShowSaveDialogIcon() { |
| return mShowSaveDialogIcon; |
| } |
| |
| /** @hide */ |
| @TestApi |
| public int getFlags() { |
| return mFlags; |
| } |
| |
| /** |
| * 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 */ |
| public int getRequestId() { |
| return mRequestId; |
| } |
| |
| /** @hide */ |
| @Nullable |
| public int[] getCancelIds() { |
| return mCancelIds; |
| } |
| |
| /** @hide */ |
| public boolean supportsInlineSuggestions() { |
| return mSupportsInlineSuggestions; |
| } |
| |
| /** |
| * 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 InlinePresentation mInlinePresentation; |
| private InlinePresentation mInlineTooltipPresentation; |
| private RemoteViews mDialogPresentation; |
| private RemoteViews mDialogHeader; |
| private RemoteViews mHeader; |
| private RemoteViews mFooter; |
| private IntentSender mAuthentication; |
| private AutofillId[] mAuthenticationIds; |
| private AutofillId[] mIgnoredIds; |
| private long mDisableDuration; |
| private AutofillId[] mFieldClassificationIds; |
| private AutofillId[] mFillDialogTriggerIds; |
| private int mFlags; |
| private boolean mDestroyed; |
| private UserData mUserData; |
| private int[] mCancelIds; |
| private boolean mSupportsInlineSuggestions; |
| private int mIconResourceId; |
| private int mServiceDisplayNameResourceId; |
| private boolean mShowFillDialogIcon = true; |
| private boolean mShowSaveDialogIcon = true; |
| private FieldClassification[] mDetectedFieldTypes; |
| private PendingIntent mDialogPendingIntent; |
| |
| /** |
| * Adds a new {@link FieldClassification} to this response, to |
| * help the platform provide more accurate detection results. |
| * |
| * Call this when a field has been detected with a type. |
| * |
| * Altough similiarly named with {@link #setFieldClassificationIds}, |
| * it provides a different functionality - setFieldClassificationIds should |
| * be used when a field is only suspected to be Autofillable. |
| * This method should be used when a field is certainly Autofillable |
| * with a certain type. |
| */ |
| @NonNull |
| public Builder setDetectedFieldClassifications( |
| @NonNull Set<FieldClassification> fieldInfos) { |
| throwIfDestroyed(); |
| throwIfDisableAutofillCalled(); |
| mDetectedFieldTypes = fieldInfos.toArray(new FieldClassification[0]); |
| return this; |
| } |
| |
| /** |
| * Triggers a custom UI 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> <b>IMPORTANT</b>: Extras must be non-null on the intent being set for Android 12 |
| * otherwise it will cause a crash. Do not use {@link Activity#setResult(int)}, instead use |
| * {@link Activity#setResult(int, Intent) with non-null extras. Consider setting { |
| * @link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} to null or use |
| * {@link Bundle#EMPTY} with {@link Intent#putExtras(Bundle)} on the intent when |
| * finishing activity to avoid crash). </p> |
| * |
| * <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 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() |
| * @deprecated Use |
| * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} |
| * instead. |
| */ |
| @Deprecated |
| @NonNull |
| public 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; |
| } |
| |
| /** |
| * Triggers a custom UI 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 method is similar to |
| * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts |
| * an {@link InlinePresentation} presentation which is required for authenticating through |
| * the inline autofill flow. |
| * |
| * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does |
| * not work with {@link InlinePresentation}.</p> |
| * |
| * @param authentication Intent to an activity with your authentication flow. |
| * @param presentation The presentation to visualize the response. |
| * @param inlinePresentation The inlinePresentation to visualize the response inline. |
| * @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> |
| * <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li> |
| * <li>both {@code authentication} and {@code inlinePresentation} 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() |
| * @deprecated Use |
| * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} |
| * instead. |
| */ |
| @Deprecated |
| @NonNull |
| public Builder setAuthentication(@NonNull AutofillId[] ids, |
| @Nullable IntentSender authentication, @Nullable RemoteViews presentation, |
| @Nullable InlinePresentation inlinePresentation) { |
| return setAuthentication(ids, authentication, presentation, inlinePresentation, null); |
| } |
| |
| /** |
| * Triggers a custom UI before autofilling the screen with any data set in this |
| * response. |
| * |
| * <p>This method like |
| * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)} |
| * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip. |
| * |
| * @deprecated Use |
| * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} |
| * instead. |
| */ |
| @Deprecated |
| @NonNull |
| public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, |
| @Nullable IntentSender authentication, @Nullable RemoteViews presentation, |
| @Nullable InlinePresentation inlinePresentation, |
| @Nullable InlinePresentation inlineTooltipPresentation) { |
| throwIfDestroyed(); |
| throwIfDisableAutofillCalled(); |
| return setAuthentication(ids, authentication, presentation, |
| inlinePresentation, inlineTooltipPresentation, null); |
| } |
| |
| /** |
| * Triggers a custom UI 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 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><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does |
| * not work with {@link InlinePresentation}.</p> |
| * |
| * @param ids id of Views that when focused will display the authentication UI. |
| * @param authentication Intent to an activity with your authentication flow. |
| * @param presentations The presentations to visualize the response. |
| * |
| * @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>{@code authentication} is {@code null}, but either or both of |
| * {@code presentations.getPresentation()} and |
| * {@code presentations.getInlinePresentation()} is non-{@code null}</li> |
| * <li>{@code authentication} is non-{{@code null}, but both |
| * {@code presentations.getPresentation()} and |
| * {@code presentations.getInlinePresentation()} are {@code null}</li> |
| * </ul> |
| * |
| * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a |
| * {@link #setFooter(RemoteViews) footer} are already set for this builder. |
| * |
| * @return This builder. |
| */ |
| @NonNull |
| public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, |
| @Nullable IntentSender authentication, |
| @Nullable Presentations presentations) { |
| throwIfDestroyed(); |
| throwIfDisableAutofillCalled(); |
| if (presentations == null) { |
| return setAuthentication(ids, authentication, null, null, null, null); |
| } |
| return setAuthentication(ids, authentication, |
| presentations.getMenuPresentation(), |
| presentations.getInlinePresentation(), |
| presentations.getInlineTooltipPresentation(), |
| presentations.getDialogPresentation()); |
| } |
| |
| /** |
| * Triggers a custom UI before autofilling the screen with any data set in this |
| * response. |
| */ |
| @NonNull |
| private Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, |
| @Nullable IntentSender authentication, @Nullable RemoteViews presentation, |
| @Nullable InlinePresentation inlinePresentation, |
| @Nullable InlinePresentation inlineTooltipPresentation, |
| @Nullable RemoteViews dialogPresentation) { |
| throwIfDestroyed(); |
| throwIfDisableAutofillCalled(); |
| if (mHeader != null || mFooter != null) { |
| throw new IllegalStateException("Already called #setHeader() or #setFooter()"); |
| } |
| |
| if (authentication == null ^ (presentation == null && inlinePresentation == null)) { |
| throw new IllegalArgumentException("authentication and presentation " |
| + "(dropdown or inline), must be both non-null or null"); |
| } |
| mAuthentication = authentication; |
| mPresentation = presentation; |
| mInlinePresentation = inlinePresentation; |
| mInlineTooltipPresentation = inlineTooltipPresentation; |
| mDialogPresentation = dialogPresentation; |
| mAuthenticationIds = assertValid(ids); |
| return this; |
| } |
| |
| /** |
| * 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. |
| */ |
| @NonNull |
| public Builder setIgnoredIds(AutofillId...ids) { |
| throwIfDestroyed(); |
| mIgnoredIds = ids; |
| return this; |
| } |
| |
| /** |
| * 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. |
| */ |
| @NonNull |
| public 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; |
| } |
| |
| /** |
| * @hide |
| */ |
| @NonNull |
| public Builder setDatasets(ArrayList<Dataset> dataset) { |
| mDatasets = dataset; |
| return this; |
| } |
| |
| /** |
| * 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. |
| * |
| * <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. |
| */ |
| @NonNull |
| public Builder setClientState(@Nullable Bundle clientState) { |
| throwIfDestroyed(); |
| throwIfDisableAutofillCalled(); |
| mClientState = clientState; |
| return this; |
| } |
| |
| /** |
| * 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}. |
| */ |
| @NonNull |
| 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. |
| * |
| * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and |
| * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. |
| * |
| * @return This builder. |
| */ |
| @NonNull |
| public Builder setFlags(@FillResponseFlags int flags) { |
| throwIfDestroyed(); |
| mFlags = Preconditions.checkFlagsArgument(flags, |
| FLAG_TRACK_CONTEXT_COMMITED |
| | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL |
| | FLAG_CREDENTIAL_MANAGER_RESPONSE); |
| return this; |
| } |
| |
| /** |
| * 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—for example, when the service has a list of "denylisted" |
| * 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, Presentations)}, |
| * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or |
| * {@link #setFieldClassificationIds(AutofillId...)} was already called. |
| */ |
| @NonNull |
| 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; |
| } |
| |
| /** |
| * Overwrites Save/Fill dialog header icon with a specific one specified by resource id. |
| * The image is pulled from the package, so id should be defined in the manifest. |
| * |
| * @param id {@link android.graphics.drawable.Drawable} resource id of the icon to be used. |
| * A value of 0 indicates to use the default header icon. |
| * |
| * @return this builder |
| */ |
| @NonNull |
| public Builder setIconResourceId(@DrawableRes int id) { |
| throwIfDestroyed(); |
| |
| mIconResourceId = id; |
| return this; |
| } |
| |
| /** |
| * Overrides the service name in the Save Dialog header with a specific string defined |
| * in the service provider's manifest.xml |
| * |
| * @param id Resoure Id of the custom string defined in the provider's manifest. If set |
| * to 0, the default name will be used. |
| * |
| * @return this builder |
| */ |
| @NonNull |
| public Builder setServiceDisplayNameResourceId(@StringRes int id) { |
| throwIfDestroyed(); |
| |
| mServiceDisplayNameResourceId = id; |
| return this; |
| } |
| |
| /** |
| * Whether or not to show the Autofill provider icon inside of the Fill Dialog |
| * |
| * @param show True to show, false to hide. Defaults to true. |
| * |
| * @return this builder |
| */ |
| @NonNull |
| public Builder setShowFillDialogIcon(boolean show) { |
| throwIfDestroyed(); |
| |
| mShowFillDialogIcon = show; |
| return this; |
| } |
| |
| /** |
| * Whether or not to show the Autofill provider icon inside of the Save Dialog |
| * |
| * @param show True to show, false to hide. Defaults to true. |
| * |
| * @return this builder |
| */ |
| @NonNull |
| public Builder setShowSaveDialogIcon(boolean show) { |
| throwIfDestroyed(); |
| |
| mShowSaveDialogIcon = show; |
| return this; |
| } |
| |
| /** |
| * 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 |
| * —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, Presentations) |
| * authentication} was already set for this builder. |
| */ |
| // TODO(b/69796626): make it sticky / update javadoc |
| @NonNull |
| public Builder setHeader(@NonNull RemoteViews header) { |
| throwIfDestroyed(); |
| throwIfAuthenticationCalled(); |
| mHeader = Objects.requireNonNull(header); |
| return this; |
| } |
| |
| /** |
| * 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 |
| * —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, Presentations) |
| * requires authentication}. |
| */ |
| // TODO(b/69796626): make it sticky / update javadoc |
| @NonNull |
| public Builder setFooter(@NonNull RemoteViews footer) { |
| throwIfDestroyed(); |
| throwIfAuthenticationCalled(); |
| mFooter = Objects.requireNonNull(footer); |
| return this; |
| } |
| |
| /** |
| * Sets a specific {@link UserData} for field classification for this request only. |
| * |
| * <p>Any fields in this UserData will override corresponding fields in the generic |
| * UserData object |
| * |
| * @return this builder |
| * @throws IllegalStateException if the FillResponse |
| * {@link #setAuthentication(AutofillId[], IntentSender, Presentations) |
| * requires authentication}. |
| */ |
| @NonNull |
| public Builder setUserData(@NonNull UserData userData) { |
| throwIfDestroyed(); |
| throwIfAuthenticationCalled(); |
| mUserData = Objects.requireNonNull(userData); |
| return this; |
| } |
| |
| /** |
| * Sets target resource IDs of the child view in {@link RemoteViews Presentation Template} |
| * which will cancel the session when clicked. |
| * Those targets will be respectively applied to a child of the header, footer and |
| * each {@link Dataset}. |
| * |
| * @param ids array of the resource id. Empty list or non-existing id has no effect. |
| * |
| * @return this builder |
| * |
| * @throws IllegalStateException if {@link #build()} was already called. |
| */ |
| @NonNull |
| public Builder setPresentationCancelIds(@Nullable int[] ids) { |
| throwIfDestroyed(); |
| mCancelIds = ids; |
| return this; |
| } |
| |
| /** |
| * Sets the presentation of header in fill dialog UI. The header should have |
| * a prompt for what datasets are shown in the dialog. If this is not set, |
| * the dialog only shows your application icon. |
| * |
| * More details about the fill dialog, see |
| * <a href="Dataset.html#FillDialogUI">fill dialog UI</a> |
| */ |
| @NonNull |
| public Builder setDialogHeader(@NonNull RemoteViews header) { |
| throwIfDestroyed(); |
| Objects.requireNonNull(header); |
| mDialogHeader = header; |
| return this; |
| } |
| |
| /** |
| * Sets which fields are used for the fill dialog UI. |
| * |
| * More details about the fill dialog, see |
| * <a href="Dataset.html#FillDialogUI">fill dialog UI</a> |
| * |
| * @throws IllegalStateException if {@link #build()} was already called. |
| * @throws NullPointerException if {@code ids} or any element on it is {@code null}. |
| */ |
| @NonNull |
| public Builder setFillDialogTriggerIds(@NonNull AutofillId... ids) { |
| throwIfDestroyed(); |
| Preconditions.checkArrayElementsNotNull(ids, "ids"); |
| mFillDialogTriggerIds = ids; |
| return this; |
| } |
| |
| /** |
| * Sets credential dialog pending intent. Framework will use the intent to launch the |
| * selector UI. A replacement for previous fill bottom sheet. |
| * |
| * @throws IllegalStateException if {@link #build()} was already called. |
| * @throws NullPointerException if {@code pendingIntent} is {@code null}. |
| * |
| * @hide |
| */ |
| @NonNull |
| public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) { |
| throwIfDestroyed(); |
| Preconditions.checkNotNull(pendingIntent, |
| "can't pass a null object to setDialogPendingIntent"); |
| mDialogPendingIntent = pendingIntent; |
| return this; |
| } |
| |
| /** |
| * 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, Presentations)}, |
| * {@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. |
| */ |
| @NonNull |
| 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"); |
| } |
| |
| if (mDatasets != null) { |
| for (final Dataset dataset : mDatasets) { |
| if (dataset.getFieldInlinePresentation(0) != null) { |
| mSupportsInlineSuggestions = true; |
| break; |
| } |
| } |
| } else if (mInlinePresentation != null) { |
| mSupportsInlineSuggestions = true; |
| } |
| |
| 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 (mInlinePresentation != null) { |
| builder.append(", hasInlinePresentation"); |
| } |
| if (mInlineTooltipPresentation != null) { |
| builder.append(", hasInlineTooltipPresentation"); |
| } |
| if (mDialogPresentation != null) { |
| builder.append(", hasDialogPresentation"); |
| } |
| if (mDialogHeader != null) { |
| builder.append(", hasDialogHeader"); |
| } |
| if (mHeader != null) { |
| builder.append(", hasHeader"); |
| } |
| if (mFooter != null) { |
| builder.append(", hasFooter"); |
| } |
| if (mAuthentication != null) { |
| builder.append(", hasAuthentication"); |
| } |
| if (mDialogPendingIntent != null) { |
| builder.append(", hasDialogPendingIntent"); |
| } |
| if (mAuthenticationIds != null) { |
| builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)); |
| } |
| if (mFillDialogTriggerIds != null) { |
| builder.append(", fillDialogTriggerIds=") |
| .append(Arrays.toString(mFillDialogTriggerIds)); |
| } |
| builder.append(", disableDuration=").append(mDisableDuration); |
| if (mFlags != 0) { |
| builder.append(", flags=").append(mFlags); |
| } |
| if (mFieldClassificationIds != null) { |
| builder.append(Arrays.toString(mFieldClassificationIds)); |
| } |
| if (mUserData != null) { |
| builder.append(", userData=").append(mUserData); |
| } |
| if (mCancelIds != null) { |
| builder.append(", mCancelIds=").append(mCancelIds.length); |
| } |
| builder.append(", mSupportInlinePresentations=").append(mSupportsInlineSuggestions); |
| 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(mInlinePresentation, flags); |
| parcel.writeParcelable(mInlineTooltipPresentation, flags); |
| parcel.writeParcelable(mDialogPresentation, flags); |
| parcel.writeParcelable(mDialogHeader, flags); |
| parcel.writeParcelable(mDialogPendingIntent, flags); |
| parcel.writeParcelableArray(mFillDialogTriggerIds, flags); |
| parcel.writeParcelable(mHeader, flags); |
| parcel.writeParcelable(mFooter, flags); |
| parcel.writeParcelable(mUserData, flags); |
| parcel.writeParcelableArray(mIgnoredIds, flags); |
| parcel.writeLong(mDisableDuration); |
| parcel.writeParcelableArray(mFieldClassificationIds, flags); |
| parcel.writeParcelableArray(mDetectedFieldTypes, flags); |
| parcel.writeInt(mIconResourceId); |
| parcel.writeInt(mServiceDisplayNameResourceId); |
| parcel.writeBoolean(mShowFillDialogIcon); |
| parcel.writeBoolean(mShowSaveDialogIcon); |
| parcel.writeInt(mFlags); |
| parcel.writeIntArray(mCancelIds); |
| parcel.writeInt(mRequestId); |
| } |
| |
| public static final @android.annotation.NonNull 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, android.content.pm.ParceledListSlice.class); |
| 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, android.service.autofill.SaveInfo.class)); |
| builder.setClientState(parcel.readParcelable(null, android.os.Bundle.class)); |
| |
| // Sets authentication state. |
| final AutofillId[] authenticationIds = parcel.readParcelableArray(null, |
| AutofillId.class); |
| final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class); |
| final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class); |
| final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); |
| final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); |
| final RemoteViews dialogPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class); |
| if (authenticationIds != null) { |
| builder.setAuthentication(authenticationIds, authentication, presentation, |
| inlinePresentation, inlineTooltipPresentation, dialogPresentation); |
| } |
| final RemoteViews dialogHeader = parcel.readParcelable(null, android.widget.RemoteViews.class); |
| if (dialogHeader != null) { |
| builder.setDialogHeader(dialogHeader); |
| } |
| final PendingIntent dialogPendingIntent = parcel.readParcelable(null, |
| PendingIntent.class); |
| if (dialogPendingIntent != null) { |
| builder.setDialogPendingIntent(dialogPendingIntent); |
| } |
| final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class); |
| if (triggerIds != null) { |
| builder.setFillDialogTriggerIds(triggerIds); |
| } |
| final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class); |
| if (header != null) { |
| builder.setHeader(header); |
| } |
| final RemoteViews footer = parcel.readParcelable(null, android.widget.RemoteViews.class); |
| if (footer != null) { |
| builder.setFooter(footer); |
| } |
| final UserData userData = parcel.readParcelable(null, android.service.autofill.UserData.class); |
| if (userData != null) { |
| builder.setUserData(userData); |
| } |
| |
| 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); |
| } |
| |
| final FieldClassification[] detectedFields = |
| parcel.readParcelableArray(null, FieldClassification.class); |
| if (detectedFields != null) { |
| builder.setDetectedFieldClassifications(Set.of(detectedFields)); |
| } |
| |
| builder.setIconResourceId(parcel.readInt()); |
| builder.setServiceDisplayNameResourceId(parcel.readInt()); |
| builder.setShowFillDialogIcon(parcel.readBoolean()); |
| builder.setShowSaveDialogIcon(parcel.readBoolean()); |
| builder.setFlags(parcel.readInt()); |
| final int[] cancelIds = parcel.createIntArray(); |
| builder.setPresentationCancelIds(cancelIds); |
| |
| final FillResponse response = builder.build(); |
| response.setRequestId(parcel.readInt()); |
| |
| return response; |
| } |
| |
| @Override |
| public FillResponse[] newArray(int size) { |
| return new FillResponse[size]; |
| } |
| }; |
| } |