Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 17 | package android.service.autofill; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 18 | |
Felipe Leme | 9f9ee25 | 2017-04-27 13:56:22 -0700 | [diff] [blame] | 19 | import static android.view.autofill.Helper.sDebug; |
Felipe Leme | d633f07 | 2017-02-14 10:17:17 -0800 | [diff] [blame] | 20 | |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 21 | import android.annotation.NonNull; |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 22 | import android.annotation.Nullable; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 23 | import android.content.IntentSender; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 24 | import android.os.Parcel; |
| 25 | import android.os.Parcelable; |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 26 | import android.view.autofill.AutofillId; |
| 27 | import android.view.autofill.AutofillValue; |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 28 | import android.widget.RemoteViews; |
Felipe Leme | d0b18d6 | 2017-07-24 17:08:26 -0700 | [diff] [blame] | 29 | |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 30 | import com.android.internal.util.Preconditions; |
| 31 | |
| 32 | import java.util.ArrayList; |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 33 | import java.util.regex.Pattern; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 34 | |
| 35 | /** |
Laura Davis | 43e75d9 | 2018-08-10 15:46:06 -0700 | [diff] [blame] | 36 | * <p>A <code>Dataset</code> object represents a group of fields (key / value pairs) used |
| 37 | * to autofill parts of a screen. |
| 38 | * |
| 39 | * <p>For more information about the role of datasets in the autofill workflow, read |
| 40 | * <a href="/guide/topics/text/autofill-services">Build autofill services</a> and the |
| 41 | * <code><a href="/reference/android/service/autofill/AutofillService">AutofillService</a></code> |
| 42 | * documentation. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 43 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 44 | * <a name="BasicUsage"></a> |
| 45 | * <h3>Basic usage</h3> |
| 46 | * |
| 47 | * <p>In its simplest form, a dataset contains one or more fields (comprised of |
| 48 | * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter |
| 49 | * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields |
| 50 | * (each field could have its own {@link RemoteViews presentation}, or use the default |
| 51 | * {@link RemoteViews presentation} associated with the whole dataset). |
| 52 | * |
| 53 | * <p>When an autofill service returns datasets in a {@link FillResponse} |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 54 | * and the screen input is focused in a view that is present in at least one of these datasets, |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 55 | * the Android System displays a UI containing the {@link RemoteViews presentation} of |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 56 | * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 57 | * dataset from the UI, all views in that dataset are autofilled. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 58 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 59 | * <a name="Authentication"></a> |
| 60 | * <h3>Dataset authentication</h3> |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 61 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 62 | * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates |
| 63 | * the dataset—in that case, when a dataset is selected by the user, the Android System |
| 64 | * launches an intent set by the service to "unlock" the dataset. |
| 65 | * |
| 66 | * <p>For example, when a data set contains credit card information (such as number, |
| 67 | * expiration date, and verification code), you could provide a dataset presentation saying |
| 68 | * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking |
| 69 | * the user to enter the credit card code, and if the user enters a valid code, you could then |
| 70 | * "unlock" the dataset. |
| 71 | * |
| 72 | * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example, |
| 73 | * if the activity being autofilled is an account creation screen, you could use an authenticated |
| 74 | * dataset to automatically generate a random password for the user. |
| 75 | * |
| 76 | * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset |
| 77 | * authentication mechanism. |
| 78 | * |
| 79 | * <a name="Filtering"></a> |
| 80 | * <h3>Filtering</h3> |
| 81 | * <p>The autofill UI automatically changes which values are shown based on value of the view |
| 82 | * anchoring it, following the rules below: |
| 83 | * <ol> |
| 84 | * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not |
| 85 | * {@link AutofillValue#isText() text} or is empty, all datasets are shown. |
| 86 | * <li>Datasets that have a filter regex (set through |
| 87 | * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or |
| 88 | * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose |
| 89 | * regex matches the view's text value converted to lower case are shown. |
| 90 | * <li>Datasets that do not require authentication, have a field value that is |
| 91 | * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts |
| 92 | * with the lower case value of the view's text are shown. |
| 93 | * <li>All other datasets are hidden. |
| 94 | * </ol> |
| 95 | * |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 96 | */ |
| 97 | public final class Dataset implements Parcelable { |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 98 | |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 99 | private final ArrayList<AutofillId> mFieldIds; |
| 100 | private final ArrayList<AutofillValue> mFieldValues; |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 101 | private final ArrayList<RemoteViews> mFieldPresentations; |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 102 | private final ArrayList<DatasetFieldFilter> mFieldFilters; |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 103 | private final RemoteViews mPresentation; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 104 | private final IntentSender mAuthentication; |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 105 | @Nullable String mId; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 106 | |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 107 | private Dataset(Builder builder) { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 108 | mFieldIds = builder.mFieldIds; |
| 109 | mFieldValues = builder.mFieldValues; |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 110 | mFieldPresentations = builder.mFieldPresentations; |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 111 | mFieldFilters = builder.mFieldFilters; |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 112 | mPresentation = builder.mPresentation; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 113 | mAuthentication = builder.mAuthentication; |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 114 | mId = builder.mId; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 115 | } |
| 116 | |
| 117 | /** @hide */ |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 118 | public @Nullable ArrayList<AutofillId> getFieldIds() { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 119 | return mFieldIds; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | /** @hide */ |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 123 | public @Nullable ArrayList<AutofillValue> getFieldValues() { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 124 | return mFieldValues; |
| 125 | } |
| 126 | |
| 127 | /** @hide */ |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 128 | public RemoteViews getFieldPresentation(int index) { |
| 129 | final RemoteViews customPresentation = mFieldPresentations.get(index); |
| 130 | return customPresentation != null ? customPresentation : mPresentation; |
| 131 | } |
| 132 | |
| 133 | /** @hide */ |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 134 | @Nullable |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 135 | public DatasetFieldFilter getFilter(int index) { |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 136 | return mFieldFilters.get(index); |
| 137 | } |
| 138 | |
| 139 | /** @hide */ |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 140 | public @Nullable IntentSender getAuthentication() { |
| 141 | return mAuthentication; |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | /** @hide */ |
| 145 | public boolean isEmpty() { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 146 | return mFieldIds == null || mFieldIds.isEmpty(); |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 147 | } |
| 148 | |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 149 | @Override |
| 150 | public String toString() { |
Felipe Leme | 9f9ee25 | 2017-04-27 13:56:22 -0700 | [diff] [blame] | 151 | if (!sDebug) return super.toString(); |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 152 | |
Felipe Leme | da9ea34 | 2018-03-22 15:19:51 -0700 | [diff] [blame] | 153 | final StringBuilder builder = new StringBuilder("Dataset["); |
Felipe Leme | 6f12e67 | 2017-10-20 13:04:19 -0700 | [diff] [blame] | 154 | if (mId == null) { |
Felipe Leme | da9ea34 | 2018-03-22 15:19:51 -0700 | [diff] [blame] | 155 | builder.append("noId"); |
Felipe Leme | 6f12e67 | 2017-10-20 13:04:19 -0700 | [diff] [blame] | 156 | } else { |
| 157 | // Cannot disclose id because it could contain PII. |
Felipe Leme | da9ea34 | 2018-03-22 15:19:51 -0700 | [diff] [blame] | 158 | builder.append("id=").append(mId.length()).append("_chars"); |
Felipe Leme | 6f12e67 | 2017-10-20 13:04:19 -0700 | [diff] [blame] | 159 | } |
Felipe Leme | da9ea34 | 2018-03-22 15:19:51 -0700 | [diff] [blame] | 160 | if (mFieldIds != null) { |
| 161 | builder.append(", fieldIds=").append(mFieldIds); |
| 162 | } |
| 163 | if (mFieldValues != null) { |
| 164 | builder.append(", fieldValues=").append(mFieldValues); |
| 165 | } |
| 166 | if (mFieldPresentations != null) { |
| 167 | builder.append(", fieldPresentations=").append(mFieldPresentations.size()); |
Felipe Leme | 6f12e67 | 2017-10-20 13:04:19 -0700 | [diff] [blame] | 168 | |
Felipe Leme | da9ea34 | 2018-03-22 15:19:51 -0700 | [diff] [blame] | 169 | } |
| 170 | if (mFieldFilters != null) { |
| 171 | builder.append(", fieldFilters=").append(mFieldFilters.size()); |
| 172 | } |
| 173 | if (mPresentation != null) { |
| 174 | builder.append(", hasPresentation"); |
| 175 | } |
| 176 | if (mAuthentication != null) { |
| 177 | builder.append(", hasAuthentication"); |
| 178 | } |
| 179 | return builder.append(']').toString(); |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 180 | } |
| 181 | |
| 182 | /** |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 183 | * Gets the id of this dataset. |
| 184 | * |
| 185 | * @return The id of this dataset or {@code null} if not set |
| 186 | * |
| 187 | * @hide |
| 188 | */ |
| 189 | public String getId() { |
| 190 | return mId; |
| 191 | } |
| 192 | |
| 193 | /** |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 194 | * A builder for {@link Dataset} objects. You must provide at least |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 195 | * one value for a field or set an authentication intent. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 196 | */ |
| 197 | public static final class Builder { |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 198 | private ArrayList<AutofillId> mFieldIds; |
| 199 | private ArrayList<AutofillValue> mFieldValues; |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 200 | private ArrayList<RemoteViews> mFieldPresentations; |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 201 | private ArrayList<DatasetFieldFilter> mFieldFilters; |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 202 | private RemoteViews mPresentation; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 203 | private IntentSender mAuthentication; |
| 204 | private boolean mDestroyed; |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 205 | @Nullable private String mId; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 206 | |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 207 | /** |
Svet Ganov | eb49515 | 2017-02-21 17:47:07 -0800 | [diff] [blame] | 208 | * Creates a new builder. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 209 | * |
Svet Ganov | eb49515 | 2017-02-21 17:47:07 -0800 | [diff] [blame] | 210 | * @param presentation The presentation used to visualize this dataset. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 211 | */ |
Svet Ganov | eb49515 | 2017-02-21 17:47:07 -0800 | [diff] [blame] | 212 | public Builder(@NonNull RemoteViews presentation) { |
| 213 | Preconditions.checkNotNull(presentation, "presentation must be non-null"); |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 214 | mPresentation = presentation; |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | /** |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 218 | * Creates a new builder for a dataset where each field will be visualized independently. |
| 219 | * |
| 220 | * <p>When using this constructor, fields must be set through |
Felipe Leme | 3a585a8 | 2017-10-18 15:34:26 -0700 | [diff] [blame] | 221 | * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or |
| 222 | * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}. |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 223 | */ |
| 224 | public Builder() { |
| 225 | } |
| 226 | |
| 227 | /** |
Felipe Leme | 601d220 | 2017-11-17 08:21:23 -0800 | [diff] [blame] | 228 | * Triggers a custom UI before before autofilling the screen with the contents of this |
| 229 | * dataset. |
| 230 | * |
| 231 | * <p><b>Note:</b> Although the name of this method suggests that it should be used just for |
| 232 | * authentication flow, it can be used for other advanced flows; see {@link AutofillService} |
| 233 | * for examples. |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 234 | * |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 235 | * <p>This method is called when you need to provide an authentication |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 236 | * UI for the data set. For example, when a data set contains credit card information |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 237 | * (such as number, expiration date, and verification code), you can display UI |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 238 | * asking for the verification code before filing in the data. Even if the |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 239 | * data set is completely populated the system will launch the specified authentication |
| 240 | * intent and will need your approval to fill it in. Since the data set is "locked" |
| 241 | * until the user authenticates it, typically this data set name is masked |
| 242 | * (for example, "VISA....1234"). Typically you would want to store the data set |
| 243 | * labels non-encrypted and the actual sensitive data encrypted and not in memory. |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 244 | * This allows showing the labels in the UI while involving the user if one of |
| 245 | * the items with these labels is chosen. Note that if you use sensitive data as |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 246 | * a label, for example an email address, then it should also be encrypted.</p> |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 247 | * |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 248 | * <p>When a user triggers autofill, the system launches the provided intent |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 249 | * whose extras will have the {@link |
Svetoslav Ganov | a9379d0 | 2017-05-09 17:40:24 -0700 | [diff] [blame] | 250 | * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content}, |
| 251 | * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client |
| 252 | * state}. Once you complete your authentication flow you should set the activity |
| 253 | * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated |
| 254 | * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by |
| 255 | * setting it to the {@link |
| 256 | * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you |
| 257 | * provide a dataset in the result, it will replace the authenticated dataset and |
| 258 | * will be immediately filled in. If you provide a response, it will replace the |
| 259 | * current response and the UI will be refreshed. For example, if you provided |
| 260 | * credit card information without the CVV for the data set in the {@link FillResponse |
| 261 | * response} then the returned data set should contain the CVV entry. |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 262 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 263 | * <p><b>Note:</b> Do not make the provided pending intent |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 264 | * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 265 | * platform needs to fill in the authentication arguments. |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 266 | * |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 267 | * @param authentication Intent to an activity with your authentication flow. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 268 | * @return this builder. |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 269 | * |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 270 | * @see android.app.PendingIntent |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 271 | */ |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 272 | public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { |
| 273 | throwIfDestroyed(); |
| 274 | mAuthentication = authentication; |
Felipe Leme | 436ab6a | 2016-12-15 11:56:15 -0800 | [diff] [blame] | 275 | return this; |
| 276 | } |
| 277 | |
| 278 | /** |
Felipe Leme | 9852897 | 2017-08-31 17:04:08 -0700 | [diff] [blame] | 279 | * Sets the id for the dataset so its usage can be tracked. |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 280 | * |
Felipe Leme | 9852897 | 2017-08-31 17:04:08 -0700 | [diff] [blame] | 281 | * <p>Dataset usage can be tracked for 2 purposes: |
| 282 | * |
| 283 | * <ul> |
| 284 | * <li>For statistical purposes, the service can call |
| 285 | * {@link AutofillService#getFillEventHistory()} when handling {@link |
| 286 | * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} |
| 287 | * calls. |
| 288 | * <li>For normal autofill workflow, the service can call |
| 289 | * {@link SaveRequest#getDatasetIds()} when handling |
| 290 | * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls. |
| 291 | * </ul> |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 292 | * |
| 293 | * @param id id for this dataset or {@code null} to unset. |
Felipe Leme | 9852897 | 2017-08-31 17:04:08 -0700 | [diff] [blame] | 294 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 295 | * @return this builder. |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 296 | */ |
| 297 | public @NonNull Builder setId(@Nullable String id) { |
| 298 | throwIfDestroyed(); |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 299 | mId = id; |
| 300 | return this; |
| 301 | } |
| 302 | |
| 303 | /** |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 304 | * Sets the value of a field. |
| 305 | * |
Felipe Leme | 9856b19 | 2017-10-18 16:43:52 -0700 | [diff] [blame] | 306 | * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would |
| 307 | * throw an {@link IllegalStateException} if this builder was constructed without a |
| 308 | * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and |
| 309 | * higher removed this restriction because datasets used as an |
| 310 | * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT |
| 311 | * authentication result} do not need a presentation. But if you don't set the presentation |
| 312 | * in the constructor in a dataset that is meant to be shown to the user, the autofill UI |
| 313 | * for this field will not be displayed. |
| 314 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 315 | * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and |
| 316 | * higher, datasets that require authentication can be also be filtered by passing a |
| 317 | * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. |
| 318 | * |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 319 | * @param id id returned by {@link |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 320 | * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 321 | * @param value value to be autofilled. Pass {@code null} if you do not have the value |
Svetoslav Ganov | a9379d0 | 2017-05-09 17:40:24 -0700 | [diff] [blame] | 322 | * but the target view is a logical part of the dataset. For example, if |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 323 | * the dataset needs authentication and you have no access to the value. |
| 324 | * @return this builder. |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 325 | */ |
Svetoslav Ganov | a9379d0 | 2017-05-09 17:40:24 -0700 | [diff] [blame] | 326 | public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 327 | throwIfDestroyed(); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 328 | setLifeTheUniverseAndEverything(id, value, null, null); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 329 | return this; |
| 330 | } |
| 331 | |
| 332 | /** |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 333 | * Sets the value of a field, using a custom {@link RemoteViews presentation} to |
| 334 | * visualize it. |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 335 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 336 | * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and |
| 337 | * higher, datasets that require authentication can be also be filtered by passing a |
| 338 | * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. |
| 339 | * |
Dake Gu | 36b86c2 | 2018-04-16 12:49:30 -0700 | [diff] [blame] | 340 | * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color |
| 341 | * or background color: Autofill on different platforms may have different themes. |
| 342 | * |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 343 | * @param id id returned by {@link |
| 344 | * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 345 | * @param value the value to be autofilled. Pass {@code null} if you do not have the value |
Svetoslav Ganov | a9379d0 | 2017-05-09 17:40:24 -0700 | [diff] [blame] | 346 | * but the target view is a logical part of the dataset. For example, if |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 347 | * the dataset needs authentication and you have no access to the value. |
| 348 | * @param presentation the presentation used to visualize this field. |
| 349 | * @return this builder. |
| 350 | * |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 351 | */ |
Svetoslav Ganov | a9379d0 | 2017-05-09 17:40:24 -0700 | [diff] [blame] | 352 | public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 353 | @NonNull RemoteViews presentation) { |
| 354 | throwIfDestroyed(); |
| 355 | Preconditions.checkNotNull(presentation, "presentation cannot be null"); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 356 | setLifeTheUniverseAndEverything(id, value, presentation, null); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 357 | return this; |
| 358 | } |
| 359 | |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 360 | /** |
| 361 | * Sets the value of a field using an <a href="#Filtering">explicit filter</a>. |
| 362 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 363 | * <p>This method is typically used when the dataset requires authentication and the service |
Felipe Leme | 601d220 | 2017-11-17 08:21:23 -0800 | [diff] [blame] | 364 | * does not know its value but wants to hide the dataset after the user enters a minimum |
| 365 | * number of characters. For example, if the dataset represents a credit card number and the |
| 366 | * service does not want to show the "Tap to authenticate" message until the user tapped |
| 367 | * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 368 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 369 | * <p><b>Note:</b> If the dataset requires authentication but the service knows its text |
| 370 | * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and |
| 371 | * use the value to filter. |
| 372 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 373 | * @param id id returned by {@link |
| 374 | * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. |
| 375 | * @param value the value to be autofilled. Pass {@code null} if you do not have the value |
| 376 | * but the target view is a logical part of the dataset. For example, if |
| 377 | * the dataset needs authentication and you have no access to the value. |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 378 | * @param filter regex used to determine if the dataset should be shown in the autofill UI; |
| 379 | * when {@code null}, it disables filtering on that dataset (this is the recommended |
| 380 | * approach when {@code value} is not {@code null} and field contains sensitive data |
| 381 | * such as passwords). |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 382 | * |
| 383 | * @return this builder. |
| 384 | * @throws IllegalStateException if the builder was constructed without a |
Felipe Leme | 9856b19 | 2017-10-18 16:43:52 -0700 | [diff] [blame] | 385 | * {@link RemoteViews presentation}. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 386 | */ |
| 387 | public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 388 | @Nullable Pattern filter) { |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 389 | throwIfDestroyed(); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 390 | Preconditions.checkState(mPresentation != null, |
| 391 | "Dataset presentation not set on constructor"); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 392 | setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter)); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 393 | return this; |
| 394 | } |
| 395 | |
| 396 | /** |
| 397 | * Sets the value of a field, using a custom {@link RemoteViews presentation} to |
| 398 | * visualize it and a <a href="#Filtering">explicit filter</a>. |
| 399 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 400 | * <p>This method is typically used when the dataset requires authentication and the service |
Felipe Leme | 601d220 | 2017-11-17 08:21:23 -0800 | [diff] [blame] | 401 | * does not know its value but wants to hide the dataset after the user enters a minimum |
| 402 | * number of characters. For example, if the dataset represents a credit card number and the |
| 403 | * service does not want to show the "Tap to authenticate" message until the user tapped |
| 404 | * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 405 | * |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 406 | * <p><b>Note:</b> If the dataset requires authentication but the service knows its text |
| 407 | * value it's easier to filter by calling |
| 408 | * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. |
| 409 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 410 | * @param id id returned by {@link |
| 411 | * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. |
| 412 | * @param value the value to be autofilled. Pass {@code null} if you do not have the value |
| 413 | * but the target view is a logical part of the dataset. For example, if |
| 414 | * the dataset needs authentication and you have no access to the value. |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 415 | * @param filter regex used to determine if the dataset should be shown in the autofill UI; |
| 416 | * when {@code null}, it disables filtering on that dataset (this is the recommended |
| 417 | * approach when {@code value} is not {@code null} and field contains sensitive data |
| 418 | * such as passwords). |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 419 | * @param presentation the presentation used to visualize this field. |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 420 | * |
| 421 | * @return this builder. |
| 422 | */ |
| 423 | public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 424 | @Nullable Pattern filter, @NonNull RemoteViews presentation) { |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 425 | throwIfDestroyed(); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 426 | Preconditions.checkNotNull(presentation, "presentation cannot be null"); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 427 | setLifeTheUniverseAndEverything(id, value, presentation, |
| 428 | new DatasetFieldFilter(filter)); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 429 | return this; |
| 430 | } |
| 431 | |
| 432 | private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, |
| 433 | @Nullable AutofillValue value, @Nullable RemoteViews presentation, |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 434 | @Nullable DatasetFieldFilter filter) { |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 435 | Preconditions.checkNotNull(id, "id cannot be null"); |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 436 | if (mFieldIds != null) { |
| 437 | final int existingIdx = mFieldIds.indexOf(id); |
| 438 | if (existingIdx >= 0) { |
| 439 | mFieldValues.set(existingIdx, value); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 440 | mFieldPresentations.set(existingIdx, presentation); |
Felipe Leme | bbf8549 | 2017-11-21 15:00:00 -0800 | [diff] [blame] | 441 | mFieldFilters.set(existingIdx, filter); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 442 | return; |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 443 | } |
| 444 | } else { |
| 445 | mFieldIds = new ArrayList<>(); |
| 446 | mFieldValues = new ArrayList<>(); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 447 | mFieldPresentations = new ArrayList<>(); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 448 | mFieldFilters = new ArrayList<>(); |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 449 | } |
| 450 | mFieldIds.add(id); |
| 451 | mFieldValues.add(value); |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 452 | mFieldPresentations.add(presentation); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 453 | mFieldFilters.add(filter); |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 454 | } |
| 455 | |
| 456 | /** |
Felipe Leme | 2ef19c1 | 2017-06-05 11:32:32 -0700 | [diff] [blame] | 457 | * Creates a new {@link Dataset} instance. |
| 458 | * |
| 459 | * <p>You should not interact with this builder once this method is called. |
| 460 | * |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 461 | * @throws IllegalStateException if no field was set (through |
| 462 | * {@link #setValue(AutofillId, AutofillValue)} or |
| 463 | * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}). |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 464 | * |
| 465 | * @return The built dataset. |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 466 | */ |
| 467 | public @NonNull Dataset build() { |
| 468 | throwIfDestroyed(); |
| 469 | mDestroyed = true; |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 470 | if (mFieldIds == null) { |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 471 | throw new IllegalStateException("at least one value must be set"); |
Svet Ganov | 00c771dc | 2017-02-19 00:06:22 -0800 | [diff] [blame] | 472 | } |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 473 | return new Dataset(this); |
| 474 | } |
| 475 | |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 476 | private void throwIfDestroyed() { |
| 477 | if (mDestroyed) { |
| 478 | throw new IllegalStateException("Already called #build()"); |
| 479 | } |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 480 | } |
| 481 | } |
| 482 | |
| 483 | ///////////////////////////////////// |
| 484 | // Parcelable "contract" methods. // |
| 485 | ///////////////////////////////////// |
| 486 | |
| 487 | @Override |
| 488 | public int describeContents() { |
| 489 | return 0; |
| 490 | } |
| 491 | |
| 492 | @Override |
| 493 | public void writeToParcel(Parcel parcel, int flags) { |
Svet Ganov | eb49515 | 2017-02-21 17:47:07 -0800 | [diff] [blame] | 494 | parcel.writeParcelable(mPresentation, flags); |
Sunny Goyal | 0e60f22 | 2017-09-21 21:39:20 -0700 | [diff] [blame] | 495 | parcel.writeTypedList(mFieldIds, flags); |
| 496 | parcel.writeTypedList(mFieldValues, flags); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 497 | parcel.writeTypedList(mFieldPresentations, flags); |
| 498 | parcel.writeTypedList(mFieldFilters, flags); |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 499 | parcel.writeParcelable(mAuthentication, flags); |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 500 | parcel.writeString(mId); |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 501 | } |
| 502 | |
Svet Ganov | 782043c | 2017-02-11 00:52:02 +0000 | [diff] [blame] | 503 | public static final Creator<Dataset> CREATOR = new Creator<Dataset>() { |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 504 | @Override |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 505 | public Dataset createFromParcel(Parcel parcel) { |
| 506 | // Always go through the builder to ensure the data ingested by |
| 507 | // the system obeys the contract of the builder to avoid attacks |
| 508 | // using specially crafted parcels. |
Felipe Leme | 5311206 | 2017-03-20 13:24:10 -0700 | [diff] [blame] | 509 | final RemoteViews presentation = parcel.readParcelable(null); |
| 510 | final Builder builder = (presentation == null) |
| 511 | ? new Builder() |
| 512 | : new Builder(presentation); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 513 | final ArrayList<AutofillId> ids = |
| 514 | parcel.createTypedArrayList(AutofillId.CREATOR); |
Sunny Goyal | 0e60f22 | 2017-09-21 21:39:20 -0700 | [diff] [blame] | 515 | final ArrayList<AutofillValue> values = |
| 516 | parcel.createTypedArrayList(AutofillValue.CREATOR); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 517 | final ArrayList<RemoteViews> presentations = |
| 518 | parcel.createTypedArrayList(RemoteViews.CREATOR); |
| 519 | final ArrayList<DatasetFieldFilter> filters = |
| 520 | parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); |
| 521 | for (int i = 0; i < ids.size(); i++) { |
Felipe Leme | 640f30a | 2017-03-06 15:44:06 -0800 | [diff] [blame] | 522 | final AutofillId id = ids.get(i); |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 523 | final AutofillValue value = values.get(i); |
| 524 | final RemoteViews fieldPresentation = presentations.get(i); |
| 525 | final DatasetFieldFilter filter = filters.get(i); |
Felipe Leme | 27a026a | 2017-10-02 17:27:49 -0700 | [diff] [blame] | 526 | builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter); |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 527 | } |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 528 | builder.setAuthentication(parcel.readParcelable(null)); |
Philip P. Moltmann | cc684ed | 2017-04-17 14:18:33 -0700 | [diff] [blame] | 529 | builder.setId(parcel.readString()); |
Svet Ganov | 0f4928f | 2017-02-02 20:02:51 -0800 | [diff] [blame] | 530 | return builder.build(); |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 531 | } |
| 532 | |
| 533 | @Override |
| 534 | public Dataset[] newArray(int size) { |
| 535 | return new Dataset[size]; |
| 536 | } |
| 537 | }; |
Felipe Leme | 09d58a4 | 2018-01-30 14:04:03 -0800 | [diff] [blame] | 538 | |
| 539 | /** |
| 540 | * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a |
| 541 | * dataset field‐ we cannot use a {@link Pattern} directly because then we wouldn't be |
| 542 | * able to differentiate whether the service explicitly passed a {@code null} filter to disable |
| 543 | * filter, or when it called the methods that does not take a filter {@link Pattern}. |
| 544 | * |
| 545 | * @hide |
| 546 | */ |
| 547 | public static final class DatasetFieldFilter implements Parcelable { |
| 548 | |
| 549 | @Nullable |
| 550 | public final Pattern pattern; |
| 551 | |
| 552 | private DatasetFieldFilter(@Nullable Pattern pattern) { |
| 553 | this.pattern = pattern; |
| 554 | } |
| 555 | |
| 556 | @Override |
| 557 | public String toString() { |
| 558 | if (!sDebug) return super.toString(); |
| 559 | |
| 560 | // Cannot log pattern because it could contain PII |
| 561 | return pattern == null ? "null" : pattern.pattern().length() + "_chars"; |
| 562 | } |
| 563 | |
| 564 | @Override |
| 565 | public int describeContents() { |
| 566 | return 0; |
| 567 | } |
| 568 | |
| 569 | @Override |
| 570 | public void writeToParcel(Parcel parcel, int flags) { |
| 571 | parcel.writeSerializable(pattern); |
| 572 | } |
| 573 | |
| 574 | @SuppressWarnings("hiding") |
| 575 | public static final Creator<DatasetFieldFilter> CREATOR = |
| 576 | new Creator<DatasetFieldFilter>() { |
| 577 | |
| 578 | @Override |
| 579 | public DatasetFieldFilter createFromParcel(Parcel parcel) { |
| 580 | return new DatasetFieldFilter((Pattern) parcel.readSerializable()); |
| 581 | } |
| 582 | |
| 583 | @Override |
| 584 | public DatasetFieldFilter[] newArray(int size) { |
| 585 | return new DatasetFieldFilter[size]; |
| 586 | } |
| 587 | }; |
| 588 | } |
Felipe Leme | 6d55387 | 2016-12-08 17:13:25 -0800 | [diff] [blame] | 589 | } |