1 /* 2 * Copyright (C) 2017 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.sVerbose; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.IntentSender; 25 import android.os.Bundle; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.util.ArrayMap; 29 import android.util.ArraySet; 30 import android.util.Log; 31 import android.view.autofill.AutofillId; 32 import android.view.autofill.AutofillManager; 33 34 import com.android.internal.util.ArrayUtils; 35 import com.android.internal.util.Preconditions; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 46 /** 47 * Describes what happened after the last 48 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 49 * call. 50 * 51 * <p>This history is typically used to keep track of previous user actions to optimize further 52 * requests. For example, the service might return email addresses in alphabetical order by 53 * default, but change that order based on the address the user picked on previous requests. 54 * 55 * <p>The history is not persisted over reboots, and it's cleared every time the service 56 * replies to a 57 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 58 * by calling {@link FillCallback#onSuccess(FillResponse)} or 59 * {@link FillCallback#onFailure(CharSequence)} (if the service doesn't call any of these methods, 60 * the history will clear out after some pre-defined time). 61 */ 62 public final class FillEventHistory implements Parcelable { 63 private static final String TAG = "FillEventHistory"; 64 65 /** 66 * Not in parcel. The ID of the autofill session that created the {@link FillResponse}. 67 */ 68 private final int mSessionId; 69 70 @Nullable private final Bundle mClientState; 71 @Nullable List<Event> mEvents; 72 73 /** @hide */ 74 public int getSessionId() { 75 return mSessionId; 76 } 77 78 /** 79 * Returns the client state set in the previous {@link FillResponse}. 80 * 81 * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous 82 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 83 * , which is not necessary the same app being autofilled now. 84 * 85 * @deprecated use {@link #getEvents()} then {@link Event#getClientState()} instead. 86 */ 87 @Deprecated 88 @Nullable public Bundle getClientState() { 89 return mClientState; 90 } 91 92 /** 93 * Returns the events occurred after the latest call to 94 * {@link FillCallback#onSuccess(FillResponse)}. 95 * 96 * @return The list of events or {@code null} if non occurred. 97 */ 98 @Nullable public List<Event> getEvents() { 99 return mEvents; 100 } 101 102 /** 103 * @hide 104 */ 105 public void addEvent(Event event) { 106 if (mEvents == null) { 107 mEvents = new ArrayList<>(1); 108 } 109 mEvents.add(event); 110 } 111 112 /** 113 * @hide 114 */ 115 public FillEventHistory(int sessionId, @Nullable Bundle clientState) { 116 mClientState = clientState; 117 mSessionId = sessionId; 118 } 119 120 @Override 121 public String toString() { 122 return mEvents == null ? "no events" : mEvents.toString(); 123 } 124 125 @Override 126 public int describeContents() { 127 return 0; 128 } 129 130 @Override 131 public void writeToParcel(Parcel parcel, int flags) { 132 parcel.writeBundle(mClientState); 133 if (mEvents == null) { 134 parcel.writeInt(0); 135 } else { 136 parcel.writeInt(mEvents.size()); 137 138 int numEvents = mEvents.size(); 139 for (int i = 0; i < numEvents; i++) { 140 Event event = mEvents.get(i); 141 parcel.writeInt(event.mEventType); 142 parcel.writeString(event.mDatasetId); 143 parcel.writeBundle(event.mClientState); 144 parcel.writeStringList(event.mSelectedDatasetIds); 145 parcel.writeArraySet(event.mIgnoredDatasetIds); 146 parcel.writeTypedList(event.mChangedFieldIds); 147 parcel.writeStringList(event.mChangedDatasetIds); 148 149 parcel.writeTypedList(event.mManuallyFilledFieldIds); 150 if (event.mManuallyFilledFieldIds != null) { 151 final int size = event.mManuallyFilledFieldIds.size(); 152 for (int j = 0; j < size; j++) { 153 parcel.writeStringList(event.mManuallyFilledDatasetIds.get(j)); 154 } 155 } 156 final AutofillId[] detectedFields = event.mDetectedFieldIds; 157 parcel.writeParcelableArray(detectedFields, flags); 158 if (detectedFields != null) { 159 FieldClassification.writeArrayToParcel(parcel, 160 event.mDetectedFieldClassifications); 161 } 162 } 163 } 164 } 165 166 /** 167 * Description of an event that occured after the latest call to 168 * {@link FillCallback#onSuccess(FillResponse)}. 169 */ 170 public static final class Event { 171 /** 172 * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}. 173 * 174 * <p><b>Note: </b>on Android {@link android.os.Build.VERSION_CODES#O}, this event was also 175 * incorrectly reported after a 176 * {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was 177 * selected and the service returned a dataset in the 178 * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT} of the activity launched from that 179 * {@link IntentSender}. This behavior was fixed on Android 180 * {@link android.os.Build.VERSION_CODES#O_MR1}. 181 */ 182 public static final int TYPE_DATASET_SELECTED = 0; 183 184 /** 185 * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was 186 * selected. The dataset authenticated can be read from {@link #getDatasetId()}. 187 */ 188 public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; 189 190 /** 191 * A {@link FillResponse.Builder#setAuthentication(android.view.autofill.AutofillId[], 192 * IntentSender, android.widget.RemoteViews) fill response authentication} was selected. 193 */ 194 public static final int TYPE_AUTHENTICATION_SELECTED = 2; 195 196 /** A save UI was shown. */ 197 public static final int TYPE_SAVE_SHOWN = 3; 198 199 /** 200 * A committed autofill context for which the autofill service provided datasets. 201 * 202 * <p>This event is useful to track: 203 * <ul> 204 * <li>Which datasets (if any) were selected by the user 205 * ({@link #getSelectedDatasetIds()}). 206 * <li>Which datasets (if any) were NOT selected by the user 207 * ({@link #getIgnoredDatasetIds()}). 208 * <li>Which fields in the selected datasets were changed by the user after the dataset 209 * was selected ({@link #getChangedFields()}. 210 * <li>Which fields match the {@link UserData} set by the service. 211 * </ul> 212 * 213 * <p><b>Note: </b>This event is only generated when: 214 * <ul> 215 * <li>The autofill context is committed. 216 * <li>The service provides at least one dataset in the 217 * {@link FillResponse fill responses} associated with the context. 218 * <li>The last {@link FillResponse fill responses} associated with the context has the 219 * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag. 220 * </ul> 221 * 222 * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill 223 * contexts. 224 */ 225 public static final int TYPE_CONTEXT_COMMITTED = 4; 226 227 /** @hide */ 228 @IntDef(prefix = { "TYPE_" }, value = { 229 TYPE_DATASET_SELECTED, 230 TYPE_DATASET_AUTHENTICATION_SELECTED, 231 TYPE_AUTHENTICATION_SELECTED, 232 TYPE_SAVE_SHOWN, 233 TYPE_CONTEXT_COMMITTED 234 }) 235 @Retention(RetentionPolicy.SOURCE) 236 @interface EventIds{} 237 238 @EventIds private final int mEventType; 239 @Nullable private final String mDatasetId; 240 @Nullable private final Bundle mClientState; 241 242 // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already 243 // stores it as List 244 @Nullable private final List<String> mSelectedDatasetIds; 245 @Nullable private final ArraySet<String> mIgnoredDatasetIds; 246 247 @Nullable private final ArrayList<AutofillId> mChangedFieldIds; 248 @Nullable private final ArrayList<String> mChangedDatasetIds; 249 250 @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds; 251 @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; 252 253 @Nullable private final AutofillId[] mDetectedFieldIds; 254 @Nullable private final FieldClassification[] mDetectedFieldClassifications; 255 256 /** 257 * Returns the type of the event. 258 * 259 * @return The type of the event 260 */ 261 public int getType() { 262 return mEventType; 263 } 264 265 /** 266 * Returns the id of dataset the id was on. 267 * 268 * @return The id of dataset, or {@code null} the event is not associated with a dataset. 269 */ 270 @Nullable public String getDatasetId() { 271 return mDatasetId; 272 } 273 274 /** 275 * Returns the client state from the {@link FillResponse} used to generate this event. 276 * 277 * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous 278 * {@link 279 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}, 280 * which is not necessary the same app being autofilled now. 281 */ 282 @Nullable public Bundle getClientState() { 283 return mClientState; 284 } 285 286 /** 287 * Returns which datasets were selected by the user. 288 * 289 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 290 */ 291 @NonNull public Set<String> getSelectedDatasetIds() { 292 return mSelectedDatasetIds == null ? Collections.emptySet() 293 : new ArraySet<>(mSelectedDatasetIds); 294 } 295 296 /** 297 * Returns which datasets were NOT selected by the user. 298 * 299 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 300 */ 301 @NonNull public Set<String> getIgnoredDatasetIds() { 302 return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds; 303 } 304 305 /** 306 * Returns which fields in the selected datasets were changed by the user after the dataset 307 * was selected. 308 * 309 * <p>For example, server provides: 310 * 311 * <pre class="prettyprint"> 312 * FillResponse response = new FillResponse.Builder() 313 * .addDataset(new Dataset.Builder(presentation1) 314 * .setId("4815") 315 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 316 * .build()) 317 * .addDataset(new Dataset.Builder(presentation2) 318 * .setId("162342") 319 * .setValue(passwordId, AutofillValue.forText("D'OH")) 320 * .build()) 321 * .build(); 322 * </pre> 323 * 324 * <p>User select both datasets (for username and password) but after the fields are 325 * autofilled, user changes them to: 326 * 327 * <pre class="prettyprint"> 328 * username = "ElBarto"; 329 * password = "AyCaramba"; 330 * </pre> 331 * 332 * <p>Then the result is the following map: 333 * 334 * <pre class="prettyprint"> 335 * usernameId => "4815" 336 * passwordId => "162342" 337 * </pre> 338 * 339 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 340 * 341 * @return map map whose key is the id of the change fields, and value is the id of 342 * dataset that has that field and was selected by the user. 343 */ 344 @NonNull public Map<AutofillId, String> getChangedFields() { 345 if (mChangedFieldIds == null || mChangedDatasetIds == null) { 346 return Collections.emptyMap(); 347 } 348 349 final int size = mChangedFieldIds.size(); 350 final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size); 351 for (int i = 0; i < size; i++) { 352 changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i)); 353 } 354 return changedFields; 355 } 356 357 /** 358 * Gets the <a href="AutofillService.html#FieldClassification">field classification</a> 359 * results. 360 * 361 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the 362 * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...) 363 * field classification}. 364 */ 365 @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() { 366 if (mDetectedFieldIds == null) { 367 return Collections.emptyMap(); 368 } 369 final int size = mDetectedFieldIds.length; 370 final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size); 371 for (int i = 0; i < size; i++) { 372 final AutofillId id = mDetectedFieldIds[i]; 373 final FieldClassification fc = mDetectedFieldClassifications[i]; 374 if (sVerbose) { 375 Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc); 376 } 377 map.put(id, fc); 378 } 379 return map; 380 } 381 382 /** 383 * Returns which fields were available on datasets provided by the service but manually 384 * entered by the user. 385 * 386 * <p>For example, server provides: 387 * 388 * <pre class="prettyprint"> 389 * FillResponse response = new FillResponse.Builder() 390 * .addDataset(new Dataset.Builder(presentation1) 391 * .setId("4815") 392 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 393 * .setValue(passwordId, AutofillValue.forText("AyCaramba")) 394 * .build()) 395 * .addDataset(new Dataset.Builder(presentation2) 396 * .setId("162342") 397 * .setValue(usernameId, AutofillValue.forText("ElBarto")) 398 * .setValue(passwordId, AutofillValue.forText("D'OH")) 399 * .build()) 400 * .addDataset(new Dataset.Builder(presentation3) 401 * .setId("108") 402 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 403 * .setValue(passwordId, AutofillValue.forText("D'OH")) 404 * .build()) 405 * .build(); 406 * </pre> 407 * 408 * <p>User doesn't select a dataset but manually enters: 409 * 410 * <pre class="prettyprint"> 411 * username = "MrPlow"; 412 * password = "D'OH"; 413 * </pre> 414 * 415 * <p>Then the result is the following map: 416 * 417 * <pre class="prettyprint"> 418 * usernameId => { "4815", "108"} 419 * passwordId => { "162342", "108" } 420 * </pre> 421 * 422 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 423 * 424 * @return map map whose key is the id of the manually-entered field, and value is the 425 * ids of the datasets that have that value but were not selected by the user. 426 */ 427 @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() { 428 if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) { 429 return Collections.emptyMap(); 430 } 431 432 final int size = mManuallyFilledFieldIds.size(); 433 final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size); 434 for (int i = 0; i < size; i++) { 435 final AutofillId fieldId = mManuallyFilledFieldIds.get(i); 436 final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i); 437 manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds)); 438 } 439 return manuallyFilledFields; 440 } 441 442 /** 443 * Creates a new event. 444 * 445 * @param eventType The type of the event 446 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 447 * whole response. 448 * @param clientState The client state associated with the event. 449 * @param selectedDatasetIds The ids of datasets selected by the user. 450 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 451 * @param changedFieldIds The ids of fields changed by the user. 452 * @param changedDatasetIds The ids of the datasets that havd values matching the 453 * respective entry on {@code changedFieldIds}. 454 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 455 * and belonged to datasets. 456 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 457 * respective entry on {@code manuallyFilledFieldIds}. 458 * @param detectedFieldClassifications the field classification matches. 459 * 460 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 461 * {@code changedDatasetIds} doesn't match. 462 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 463 * {@code manuallyFilledDatasetIds} doesn't match. 464 * 465 * @hide 466 */ 467 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 468 @Nullable List<String> selectedDatasetIds, 469 @Nullable ArraySet<String> ignoredDatasetIds, 470 @Nullable ArrayList<AutofillId> changedFieldIds, 471 @Nullable ArrayList<String> changedDatasetIds, 472 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 473 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 474 @Nullable AutofillId[] detectedFieldIds, 475 @Nullable FieldClassification[] detectedFieldClassifications) { 476 mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED, 477 "eventType"); 478 mDatasetId = datasetId; 479 mClientState = clientState; 480 mSelectedDatasetIds = selectedDatasetIds; 481 mIgnoredDatasetIds = ignoredDatasetIds; 482 if (changedFieldIds != null) { 483 Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds) 484 && changedDatasetIds != null 485 && changedFieldIds.size() == changedDatasetIds.size(), 486 "changed ids must have same length and not be empty"); 487 } 488 mChangedFieldIds = changedFieldIds; 489 mChangedDatasetIds = changedDatasetIds; 490 if (manuallyFilledFieldIds != null) { 491 Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds) 492 && manuallyFilledDatasetIds != null 493 && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(), 494 "manually filled ids must have same length and not be empty"); 495 } 496 mManuallyFilledFieldIds = manuallyFilledFieldIds; 497 mManuallyFilledDatasetIds = manuallyFilledDatasetIds; 498 499 mDetectedFieldIds = detectedFieldIds; 500 mDetectedFieldClassifications = detectedFieldClassifications; 501 } 502 503 @Override 504 public String toString() { 505 return "FillEvent [datasetId=" + mDatasetId 506 + ", type=" + mEventType 507 + ", selectedDatasets=" + mSelectedDatasetIds 508 + ", ignoredDatasetIds=" + mIgnoredDatasetIds 509 + ", changedFieldIds=" + mChangedFieldIds 510 + ", changedDatasetsIds=" + mChangedDatasetIds 511 + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds 512 + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds 513 + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds) 514 + ", detectedFieldClassifications =" 515 + Arrays.toString(mDetectedFieldClassifications) 516 + "]"; 517 } 518 } 519 520 public static final Parcelable.Creator<FillEventHistory> CREATOR = 521 new Parcelable.Creator<FillEventHistory>() { 522 @Override 523 public FillEventHistory createFromParcel(Parcel parcel) { 524 FillEventHistory selection = new FillEventHistory(0, parcel.readBundle()); 525 526 final int numEvents = parcel.readInt(); 527 for (int i = 0; i < numEvents; i++) { 528 final int eventType = parcel.readInt(); 529 final String datasetId = parcel.readString(); 530 final Bundle clientState = parcel.readBundle(); 531 final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList(); 532 @SuppressWarnings("unchecked") 533 final ArraySet<String> ignoredDatasets = 534 (ArraySet<String>) parcel.readArraySet(null); 535 final ArrayList<AutofillId> changedFieldIds = 536 parcel.createTypedArrayList(AutofillId.CREATOR); 537 final ArrayList<String> changedDatasetIds = parcel.createStringArrayList(); 538 539 final ArrayList<AutofillId> manuallyFilledFieldIds = 540 parcel.createTypedArrayList(AutofillId.CREATOR); 541 final ArrayList<ArrayList<String>> manuallyFilledDatasetIds; 542 if (manuallyFilledFieldIds != null) { 543 final int size = manuallyFilledFieldIds.size(); 544 manuallyFilledDatasetIds = new ArrayList<>(size); 545 for (int j = 0; j < size; j++) { 546 manuallyFilledDatasetIds.add(parcel.createStringArrayList()); 547 } 548 } else { 549 manuallyFilledDatasetIds = null; 550 } 551 final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null, 552 AutofillId.class); 553 final FieldClassification[] detectedFieldClassifications = 554 (detectedFieldIds != null) 555 ? FieldClassification.readArrayFromParcel(parcel) 556 : null; 557 558 selection.addEvent(new Event(eventType, datasetId, clientState, 559 selectedDatasetIds, ignoredDatasets, 560 changedFieldIds, changedDatasetIds, 561 manuallyFilledFieldIds, manuallyFilledDatasetIds, 562 detectedFieldIds, detectedFieldClassifications)); 563 } 564 return selection; 565 } 566 567 @Override 568 public FillEventHistory[] newArray(int size) { 569 return new FillEventHistory[size]; 570 } 571 }; 572 } 573