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