/* * 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)}. * *
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)}. * *
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 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 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 {@link 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 {@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.
*
* 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).
*
* IMPORTANT: 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). 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.
*
* If you provide an authentication intent you must also provide a presentation
* which is used to visualize the response for triggering the authentication
* flow.
*
* 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.
*
* 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:
* 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 {@link AutofillService}
* for examples.
*
* 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.
*
* Note: {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
* not work with {@link InlinePresentation}. 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.
*
* 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 {@link 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 {@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.
*
* 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).
*
* 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.
*
* If you provide an authentication intent you must also provide a presentation
* which is used to visualize the response for triggering the authentication
* flow.
*
* 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.
*
* Note: {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
* not work with {@link InlinePresentation}. 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.
*
* Note: 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 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()}.
*
* 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
* field classification
*
* Note: 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.
*
* 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.
*
* 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}.
*
* Autofill for the app or activity is automatically re-enabled after any of the
* following conditions:
*
* Note: 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.
*
* 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).
*
* 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.
*
* 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).
*
* 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.
*
* 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
* fill dialog UI
*/
@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
* fill dialog UI
*
* @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:
*
*
*
* @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.
*
*
*
*
* @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.
*
*
*
*
* @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.
*
*
*
*
*
*
*
* @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