/* * 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.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.ClipData; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.widget.RemoteViews; import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.regex.Pattern; /** *
A Dataset
object represents a group of fields (key / value pairs) used
* to autofill parts of a screen.
*
*
For more information about the role of datasets in the autofill workflow, read
* Build autofill services and the
* AutofillService
* documentation.
*
*
*
In its simplest form, a dataset contains one or more fields (comprised of * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields * (each field could have its own {@link RemoteViews presentation}, or use the default * {@link RemoteViews presentation} associated with the whole dataset). * *
When an autofill service returns datasets in a {@link FillResponse} * and the screen input is focused in a view that is present in at least one of these datasets, * the Android System displays a UI containing the {@link RemoteViews presentation} of * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a * dataset from the UI, all views in that dataset are autofilled. * *
If both the current Input Method and autofill service supports inline suggestions, the Dataset * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered. * * *
In a more sophisticated form, the dataset values can be protected until the user authenticates * the dataset—in that case, when a dataset is selected by the user, the Android System * launches an intent set by the service to "unlock" the dataset. * *
For example, when a data set contains credit card information (such as number, * expiration date, and verification code), you could provide a dataset presentation saying * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking * the user to enter the credit card code, and if the user enters a valid code, you could then * "unlock" the dataset. * *
You can also use authenticated datasets to offer an interactive UI for the user. For example, * if the activity being autofilled is an account creation screen, you could use an authenticated * dataset to automatically generate a random password for the user. * *
See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset * authentication mechanism. * * *
The autofill UI automatically changes which values are shown based on value of the view * anchoring it, following the rules below: *
Only called by augmented autofill. * * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, * this should not be null. * @hide */ @SystemApi public Builder(@NonNull InlinePresentation inlinePresentation) { Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mInlinePresentation = inlinePresentation; } /** * Creates a new builder for a dataset where each field will be visualized independently. * *
When using this constructor, fields must be set through * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}. */ public Builder() { } /** * Sets the {@link InlinePresentation} used to visualize this dataset as inline suggestions. * If the dataset supports inline suggestions this should not be null. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setInlinePresentation( @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mInlinePresentation = inlinePresentation; return this; } /** * Visualizes this dataset as inline suggestions. * * @param inlinePresentation the {@link InlinePresentation} used to visualize this * dataset as inline suggestions. If the dataset supports inline suggestions this * should not be null. * @param inlineTooltipPresentation the {@link InlinePresentation} used to show * the tooltip for the {@code inlinePresentation}. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setInlinePresentation( @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); Preconditions.checkNotNull(inlineTooltipPresentation, "inlineTooltipPresentation must be non-null"); mInlinePresentation = inlinePresentation; mInlineTooltipPresentation = inlineTooltipPresentation; return this; } /** * Triggers a custom UI before before autofilling the screen with the contents of this * dataset. * *
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 called when you need to provide an authentication * UI for the data set. For example, when a data set contains credit card information * (such as number, expiration date, and verification code), you can display UI * asking for the verification code before filing in the data. Even if the * data set is completely populated the system will launch the specified authentication * intent and will need your approval to fill it in. Since the data set is "locked" * until the user authenticates it, typically this data set name is masked * (for example, "VISA....1234"). Typically you would want to store the data set * labels non-encrypted and the actual sensitive data encrypted and not in memory. * This allows showing the labels in the UI while involving the user if one of * the items with these labels is chosen. Note that if you use sensitive data as * a label, for example an email address, then it should also be encrypted.
* *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 activity * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by * setting it to the {@link * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you * provide a dataset in the result, it will replace the authenticated dataset and * will be immediately filled in. An exception to this behavior is if the original * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then * the original dataset will not be replaced, * so that it can be triggered as a pending intent again. * If you provide a response, it will replace the * current response and the UI will be refreshed. For example, if you provided * credit card information without the CVV for the data set in the {@link FillResponse * response} then the returned data set should contain the CVV entry. * *
Note: Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. * * @see android.app.PendingIntent */ public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { throwIfDestroyed(); mAuthentication = authentication; return this; } /** * Sets the id for the dataset so its usage can be tracked. * *
Dataset usage can be tracked for 2 purposes: * *
Only called by augmented autofill. * *
For a given field, either a {@link AutofillValue value} or content can be filled, but * not both. Furthermore, when filling content, only a single field can be filled. * *
The provided {@link ClipData} can contain content URIs (e.g. a URI for an image). * The augmented autofill provider setting the content here must itself have at least * read permissions to any passed content URIs. If the user accepts the suggestion backed * by the content URI(s), the platform will automatically grant read URI permissions to * the app being autofilled, just before passing the content URI(s) to it. The granted * permissions will be transient and tied to the lifecycle of the activity being filled * (when the activity finishes, permissions will automatically be revoked by the platform). * * @param id id returned by * {@link android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param content content to be autofilled. Pass {@code null} if you do not have the content * but the target view is a logical part of the dataset. For example, if the dataset needs * authentication. * * @throws IllegalStateException if {@link #build()} was already called. * @throws IllegalArgumentException if the provided content * {@link ClipData.Item#getIntent() contains an intent} * * @return this builder. * * @hide */ @TestApi @SystemApi @SuppressLint("MissingGetterMatchingBuilder") public @NonNull Builder setContent(@NonNull AutofillId id, @Nullable ClipData content) { throwIfDestroyed(); if (content != null) { for (int i = 0; i < content.getItemCount(); i++) { Preconditions.checkArgument(content.getItemAt(i).getIntent() == null, "Content items cannot contain an Intent: content=" + content); } } setLifeTheUniverseAndEverything(id, null, null, null, null); mFieldContent = content; return this; } /** * Sets the value of a field. * * Note: Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would * throw an {@link IllegalStateException} if this builder was constructed without a * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and * higher removed this restriction because datasets used as an * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT * authentication result} do not need a presentation. But if you don't set the presentation * in the constructor in a dataset that is meant to be shown to the user, the autofill UI * for this field will not be displayed. * *
Note: On Android {@link android.os.Build.VERSION_CODES#P} and * higher, datasets that require authentication can be also be filtered by passing a * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { throwIfDestroyed(); setLifeTheUniverseAndEverything(id, value, null, null, null); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it. * *
Note: On Android {@link android.os.Build.VERSION_CODES#P} and * higher, datasets that require authentication can be also be filtered by passing a * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. * *
Theme does not work with RemoteViews layout. Avoid hardcoded text color * or background color: Autofill on different platforms may have different themes. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, null, null); return this; } /** * Sets the value of a field using an explicit filter. * *
This method is typically used when the dataset requires authentication and the service * does not know its value but wants to hide the dataset after the user enters a minimum * number of characters. For example, if the dataset represents a credit card number and the * service does not want to show the "Tap to authenticate" message until the user tapped * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. * *
Note: If the dataset requires authentication but the service knows its text * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and * use the value to filter. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI; * when {@code null}, it disables filtering on that dataset (this is the recommended * approach when {@code value} is not {@code null} and field contains sensitive data * such as passwords). * * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation} or {@link #build()} was already called. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter) { throwIfDestroyed(); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter)); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a explicit filter. * *
This method is typically used when the dataset requires authentication and the service * does not know its value but wants to hide the dataset after the user enters a minimum * number of characters. For example, if the dataset represents a credit card number and the * service does not want to show the "Tap to authenticate" message until the user tapped * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. * *
Note: If the dataset requires authentication but the service knows its text * value it's easier to filter by calling * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI; * when {@code null}, it disables filtering on that dataset (this is the recommended * approach when {@code value} is not {@code null} and field contains sensitive data * such as passwords). * @param presentation the presentation used to visualize this field. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, null, new DatasetFieldFilter(filter)); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. * *
Note: If the dataset requires authentication but the service knows its text * value it's easier to filter by calling * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, * this should not be null. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. * * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation) * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param presentation the presentation used to visualize this field. * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, * this should not be null. * @param inlineTooltipPresentation The {@link InlinePresentation} used to show * the tooltip for the {@code inlinePresentation}. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); Preconditions.checkNotNull(inlineTooltipPresentation, "inlineTooltipPresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, inlineTooltipPresentation, null); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a explicit filter, and an * {@link InlinePresentation} to visualize it as an inline suggestion. * *
This method is typically used when the dataset requires authentication and the service * does not know its value but wants to hide the dataset after the user enters a minimum * number of characters. For example, if the dataset represents a credit card number and the * service does not want to show the "Tap to authenticate" message until the user tapped * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. * *
Note: If the dataset requires authentication but the service knows its text * value it's easier to filter by calling * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI; * when {@code null}, it disables filtering on that dataset (this is the recommended * approach when {@code value} is not {@code null} and field contains sensitive data * such as passwords). * @param presentation the presentation used to visualize this field. * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, this * should not be null. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, new DatasetFieldFilter(filter)); return this; } /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a explicit filter, and an * {@link InlinePresentation} to visualize it as an inline suggestion. * * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation) * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI; * when {@code null}, it disables filtering on that dataset (this is the recommended * approach when {@code value} is not {@code null} and field contains sensitive data * such as passwords). * @param presentation the presentation used to visualize this field. * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, this * should not be null. * @param inlineTooltipPresentation The {@link InlinePresentation} used to show * the tooltip for the {@code inlinePresentation}. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); Preconditions.checkNotNull(presentation, "presentation cannot be null"); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); Preconditions.checkNotNull(inlineTooltipPresentation, "inlineTooltipPresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, inlineTooltipPresentation, new DatasetFieldFilter(filter)); return this; } /** * Sets the value of a field with an explicit filter, and using an * {@link InlinePresentation} to visualize it as an inline suggestion. * *
Only called by augmented autofill. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. * @param filter regex used to determine if the dataset should be shown in the autofill UI; * when {@code null}, it disables filtering on that dataset (this is the recommended * approach when {@code value} is not {@code null} and field contains sensitive data * such as passwords). * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset * as inline suggestions. If the dataset supports inline suggestions, this * should not be null. * * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. * * @hide */ @SystemApi public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, new DatasetFieldFilter(filter)); return this; } private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable DatasetFieldFilter filter) { setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, filter); } private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); if (existingIdx >= 0) { mFieldValues.set(existingIdx, value); mFieldPresentations.set(existingIdx, presentation); mFieldInlinePresentations.set(existingIdx, inlinePresentation); mFieldInlineTooltipPresentations.set(existingIdx, tooltip); mFieldFilters.set(existingIdx, filter); return; } } else { mFieldIds = new ArrayList<>(); mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); mFieldInlinePresentations = new ArrayList<>(); mFieldInlineTooltipPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); } mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldInlinePresentations.add(inlinePresentation); mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); } /** * Creates a new {@link Dataset} instance. * *
You should not interact with this builder once this method is called.
*
* @throws IllegalStateException if no field was set (through
* {@link #setValue(AutofillId, AutofillValue)} or
* {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
* {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}),
* or if {@link #build()} was already called.
*
* @return The built dataset.
*/
public @NonNull Dataset build() {
throwIfDestroyed();
mDestroyed = true;
if (mFieldIds == null) {
throw new IllegalStateException("at least one value must be set");
}
if (mFieldContent != null) {
if (mFieldIds.size() > 1) {
throw new IllegalStateException(
"when filling content, only one field can be filled");
}
if (mFieldValues.get(0) != null) {
throw new IllegalStateException("cannot fill both content and values");
}
}
return new Dataset(this);
}
private void throwIfDestroyed() {
if (mDestroyed) {
throw new IllegalStateException("Already called #build()");
}
}
}
/////////////////////////////////////
// Parcelable "contract" methods. //
/////////////////////////////////////
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeTypedList(mFieldPresentations, flags);
parcel.writeTypedList(mFieldInlinePresentations, flags);
parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
parcel.writeTypedList(mFieldFilters, flags);
parcel.writeParcelable(mFieldContent, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
}
public static final @NonNull Creator