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.view.autofill; 18 19 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING; 20 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 21 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; 22 import static android.service.autofill.FillRequest.FLAG_PCC_DETECTION; 23 import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE; 24 import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD; 25 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; 26 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; 27 import static android.view.ContentInfo.SOURCE_AUTOFILL; 28 import static android.view.autofill.Helper.sDebug; 29 import static android.view.autofill.Helper.sVerbose; 30 import static android.view.autofill.Helper.toList; 31 32 import android.accessibilityservice.AccessibilityServiceInfo; 33 import android.annotation.IntDef; 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.RequiresFeature; 37 import android.annotation.SystemApi; 38 import android.annotation.SystemService; 39 import android.annotation.TestApi; 40 import android.app.assist.AssistStructure.ViewNode; 41 import android.app.assist.AssistStructure.ViewNodeBuilder; 42 import android.app.assist.AssistStructure.ViewNodeParcelable; 43 import android.content.AutofillOptions; 44 import android.content.ClipData; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.content.IntentSender; 49 import android.content.pm.PackageManager; 50 import android.content.pm.ResolveInfo; 51 import android.graphics.Rect; 52 import android.metrics.LogMaker; 53 import android.os.Build; 54 import android.os.Bundle; 55 import android.os.Handler; 56 import android.os.IBinder; 57 import android.os.Looper; 58 import android.os.Parcelable; 59 import android.os.RemoteException; 60 import android.os.SystemClock; 61 import android.service.autofill.AutofillService; 62 import android.service.autofill.FillEventHistory; 63 import android.service.autofill.UserData; 64 import android.text.TextUtils; 65 import android.util.ArrayMap; 66 import android.util.ArraySet; 67 import android.util.DebugUtils; 68 import android.util.Log; 69 import android.util.Slog; 70 import android.util.SparseArray; 71 import android.view.Choreographer; 72 import android.view.ContentInfo; 73 import android.view.KeyEvent; 74 import android.view.View; 75 import android.view.ViewRootImpl; 76 import android.view.WindowInsets; 77 import android.view.WindowManager; 78 import android.view.accessibility.AccessibilityEvent; 79 import android.view.accessibility.AccessibilityManager; 80 import android.view.accessibility.AccessibilityNodeInfo; 81 import android.view.accessibility.AccessibilityNodeProvider; 82 import android.view.accessibility.AccessibilityWindowInfo; 83 import android.view.inputmethod.InputMethodManager; 84 import android.widget.CheckBox; 85 import android.widget.DatePicker; 86 import android.widget.EditText; 87 import android.widget.RadioGroup; 88 import android.widget.Spinner; 89 import android.widget.TextView; 90 import android.widget.TimePicker; 91 92 import com.android.internal.annotations.GuardedBy; 93 import com.android.internal.logging.MetricsLogger; 94 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 95 import com.android.internal.os.IResultReceiver; 96 import com.android.internal.util.ArrayUtils; 97 import com.android.internal.util.SyncResultReceiver; 98 99 import org.xmlpull.v1.XmlPullParserException; 100 101 import java.io.IOException; 102 import java.io.PrintWriter; 103 import java.lang.annotation.Retention; 104 import java.lang.annotation.RetentionPolicy; 105 import java.lang.ref.WeakReference; 106 import java.util.ArrayList; 107 import java.util.Arrays; 108 import java.util.Collections; 109 import java.util.List; 110 import java.util.Objects; 111 import java.util.Set; 112 import java.util.concurrent.atomic.AtomicBoolean; 113 114 import sun.misc.Cleaner; 115 116 //TODO: use java.lang.ref.Cleaner once Android supports Java 9 117 118 /** 119 * <p>The {@link AutofillManager} class provides ways for apps and custom views to 120 * integrate with the Autofill Framework lifecycle. 121 * 122 * <p>To learn about using Autofill in your app, read 123 * the <a href="/guide/topics/text/autofill">Autofill Framework</a> guides. 124 * 125 * <h3 id="autofill-lifecycle">Autofill lifecycle</h3> 126 * 127 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an 128 * activity context. The autofill context is created when one of the following methods is called for 129 * the first time in an activity context, and the current user has an enabled autofill service: 130 * 131 * <ul> 132 * <li>{@link #notifyViewEntered(View)} 133 * <li>{@link #notifyViewEntered(View, int, Rect)} 134 * <li>{@link #requestAutofill(View)} 135 * </ul> 136 * 137 * <p>Typically, the context is automatically created when the first view of the activity is 138 * focused because {@code View.onFocusChanged()} indirectly calls 139 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to 140 * explicitly create it (for example, a custom view developer could offer a contextual menu action 141 * in a text-field view to let users manually request autofill). 142 * 143 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure} 144 * that represents the view hierarchy by calling 145 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views 146 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in 147 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and 148 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in 149 * the hierarchy. 150 * 151 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which 152 * parses it looking for views that can be autofilled. If the service finds such views, it returns 153 * a data structure to the Android System containing the following optional info: 154 * 155 * <ul> 156 * <li>Datasets used to autofill subsets of views in the activity. 157 * <li>Id of views that the service can save their values for future autofilling. 158 * </ul> 159 * 160 * <p>When the service returns datasets, the Android System displays an autofill dataset picker 161 * UI associated with the view, when the view is focused on and is part of a dataset. 162 * The application can be notified when the UI is shown by registering an 163 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user 164 * selects a dataset from the UI, all views present in the dataset are autofilled, through 165 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. 166 * 167 * <p>When the service returns ids of savable views, the Android System keeps track of changes 168 * made to these views, so they can be used to determine if the autofill save UI is shown later. 169 * 170 * <p>The context is then finished when one of the following occurs: 171 * 172 * <ul> 173 * <li>{@link #commit()} is called or all savable views are gone. 174 * <li>{@link #cancel()} is called. 175 * </ul> 176 * 177 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System 178 * shows an autofill save UI if the value of savable views have changed. If the user selects the 179 * option to Save, the current value of the views is then sent to the autofill service. 180 * 181 * <h3 id="additional-notes">Additional notes</h3> 182 * 183 * <p>It is safe to call <code>AutofillManager</code> methods from any thread. 184 */ 185 @SystemService(Context.AUTOFILL_MANAGER_SERVICE) 186 @RequiresFeature(PackageManager.FEATURE_AUTOFILL) 187 public final class AutofillManager { 188 189 private static final String TAG = "AutofillManager"; 190 191 /** 192 * Intent extra: The assist structure which captures the filled screen. 193 * 194 * <p> 195 * Type: {@link android.app.assist.AssistStructure} 196 */ 197 public static final String EXTRA_ASSIST_STRUCTURE = 198 "android.view.autofill.extra.ASSIST_STRUCTURE"; 199 200 /** 201 * Intent extra: The result of an authentication operation. It is 202 * either a fully populated {@link android.service.autofill.FillResponse} 203 * or a fully populated {@link android.service.autofill.Dataset} if 204 * a response or a dataset is being authenticated respectively. 205 * 206 * <p> 207 * Type: {@link android.service.autofill.FillResponse} or a 208 * {@link android.service.autofill.Dataset} 209 */ 210 public static final String EXTRA_AUTHENTICATION_RESULT = 211 "android.view.autofill.extra.AUTHENTICATION_RESULT"; 212 213 /** 214 * Intent extra: The optional boolean extra field provided by the 215 * {@link android.service.autofill.AutofillService} accompanying the {@link 216 * android.service.autofill.Dataset} result of an authentication operation. 217 * 218 * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a 219 * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also 220 * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}. 221 * That means if the user clears the field values, the autofill suggestion will show up again 222 * with the new authenticated Dataset. 223 * 224 * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior 225 * that if the Dataset being authenticated is a pinned dataset (see 226 * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be 227 * replaced. 228 * 229 * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to 230 * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether 231 * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to 232 * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the 233 * returned Dataset will not replace the old dataset from the existing 234 * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not 235 * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is 236 * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}. 237 */ 238 public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = 239 "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET"; 240 241 /** 242 * Intent extra: The optional extras provided by the 243 * {@link android.service.autofill.AutofillService}. 244 * 245 * <p>For example, when the service responds to a {@link 246 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with 247 * a {@code FillResponse} that requires authentication, the Intent that launches the 248 * service authentication will contain the Bundle set by 249 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. 250 * 251 * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service 252 * can also add this bundle to the {@link Intent} set as the 253 * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, 254 * so the bundle can be recovered later on 255 * {@link android.service.autofill.SaveRequest#getClientState()}. 256 * 257 * <p> 258 * Type: {@link android.os.Bundle} 259 */ 260 public static final String EXTRA_CLIENT_STATE = 261 "android.view.autofill.extra.CLIENT_STATE"; 262 263 /** 264 * Intent extra: the {@link android.view.inputmethod.InlineSuggestionsRequest} in the 265 * autofill request. 266 * 267 * <p>This is filled in the authentication intent so the 268 * {@link android.service.autofill.AutofillService} can use it to create the inline 269 * suggestion {@link android.service.autofill.Dataset} in the response, if the original autofill 270 * request contains the {@link android.view.inputmethod.InlineSuggestionsRequest}. 271 */ 272 public static final String EXTRA_INLINE_SUGGESTIONS_REQUEST = 273 "android.view.autofill.extra.INLINE_SUGGESTIONS_REQUEST"; 274 275 /** @hide */ 276 public static final String EXTRA_RESTORE_SESSION_TOKEN = 277 "android.view.autofill.extra.RESTORE_SESSION_TOKEN"; 278 279 /** @hide */ 280 public static final String EXTRA_RESTORE_CROSS_ACTIVITY = 281 "android.view.autofill.extra.RESTORE_CROSS_ACTIVITY"; 282 283 /** 284 * Internal extra used to pass a binder to the {@link IAugmentedAutofillManagerClient}. 285 * 286 * @hide 287 */ 288 public static final String EXTRA_AUGMENTED_AUTOFILL_CLIENT = 289 "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT"; 290 291 /** 292 * Autofill Hint to indicate that it can match any field. 293 * 294 * @hide 295 */ 296 @TestApi 297 public static final String ANY_HINT = "any"; 298 299 private static final String SESSION_ID_TAG = "android:sessionId"; 300 private static final String STATE_TAG = "android:state"; 301 private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; 302 303 /** @hide */ public static final int ACTION_START_SESSION = 1; 304 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2; 305 /** @hide */ public static final int ACTION_VIEW_EXITED = 3; 306 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; 307 /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5; 308 309 /** @hide */ public static final int NO_LOGGING = 0; 310 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; 311 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; 312 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; 313 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8; 314 315 // NOTE: flag below is used by the session start receiver only, hence it can have values above 316 /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; 317 318 /** @hide */ 319 public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE 320 ? AutofillManager.FLAG_ADD_CLIENT_DEBUG 321 : AutofillManager.NO_LOGGING; 322 323 /** @hide */ 324 public static final int DEFAULT_MAX_PARTITIONS_SIZE = 10; 325 326 /** Which bits in an authentication id are used for the dataset id */ 327 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF; 328 /** How many bits in an authentication id are used for the dataset id */ 329 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16; 330 /** @hide The index for an undefined data set */ 331 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF; 332 333 /** 334 * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI. 335 * 336 * @hide 337 */ 338 public static final int PENDING_UI_OPERATION_CANCEL = 1; 339 340 /** 341 * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI. 342 * 343 * @hide 344 */ 345 public static final int PENDING_UI_OPERATION_RESTORE = 2; 346 347 /** 348 * Initial state of the autofill context, set when there is no session (i.e., when 349 * {@link #mSessionId} is {@link #NO_SESSION}). 350 * 351 * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to 352 * the server. 353 * 354 * @hide 355 */ 356 public static final int STATE_UNKNOWN = 0; 357 358 /** 359 * State where the autofill context hasn't been {@link #commit() finished} nor 360 * {@link #cancel() canceled} yet. 361 * 362 * @hide 363 */ 364 public static final int STATE_ACTIVE = 1; 365 366 /** 367 * State where the autofill context was finished by the server because the autofill 368 * service could not autofill the activity. 369 * 370 * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, 371 * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). 372 * 373 * @hide 374 */ 375 public static final int STATE_FINISHED = 2; 376 377 /** 378 * State where the autofill context has been {@link #commit() finished} but the server still has 379 * a session because the Save UI hasn't been dismissed yet. 380 * 381 * @hide 382 */ 383 public static final int STATE_SHOWING_SAVE_UI = 3; 384 385 /** 386 * State where the autofill is disabled because the service cannot autofill the activity at all. 387 * 388 * <p>In this state, every call is ignored, even {@link #requestAutofill(View)} 389 * (and {@link #requestAutofill(View, int, Rect)}). 390 * 391 * @hide 392 */ 393 public static final int STATE_DISABLED_BY_SERVICE = 4; 394 395 /** 396 * Same as {@link #STATE_UNKNOWN}, but used on 397 * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished 398 * because the URL bar changed on client mode 399 * 400 * @hide 401 */ 402 public static final int STATE_UNKNOWN_COMPAT_MODE = 5; 403 404 /** 405 * Same as {@link #STATE_UNKNOWN}, but used on 406 * {@link AutofillManagerClient#setSessionFinished(int, List)} when the session was finished 407 * because the service failed to fullfil a request. 408 * 409 * @hide 410 */ 411 public static final int STATE_UNKNOWN_FAILED = 6; 412 413 /** 414 * Timeout in ms for calls to the field classification service. 415 * @hide 416 */ 417 public static final int FC_SERVICE_TIMEOUT = 5000; 418 419 /** 420 * Timeout for calls to system_server. 421 */ 422 private static final int SYNC_CALLS_TIMEOUT_MS = 5000; 423 424 /** 425 * @hide 426 */ 427 @TestApi 428 public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes 429 430 /** 431 * Disables Augmented Autofill. 432 * 433 * @hide 434 */ 435 @TestApi 436 public static final int FLAG_SMART_SUGGESTION_OFF = 0x0; 437 438 /** 439 * Displays the Augment Autofill window using the same mechanism (such as a popup-window 440 * attached to the focused view) as the standard autofill. 441 * 442 * @hide 443 */ 444 @TestApi 445 public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1; 446 447 /** @hide */ 448 @IntDef(flag = false, value = { FLAG_SMART_SUGGESTION_OFF, FLAG_SMART_SUGGESTION_SYSTEM }) 449 @Retention(RetentionPolicy.SOURCE) 450 public @interface SmartSuggestionMode {} 451 452 /** @hide */ 453 public static final int RESULT_OK = 0; 454 /** @hide */ 455 public static final int RESULT_CODE_NOT_SERVICE = -1; 456 457 /** 458 * Reasons to commit the Autofill context. 459 * 460 * <p>If adding a new reason, modify 461 * {@link com.android.server.autofill.PresentationStatsEventLogger#getNoPresentationEventReason(int)} 462 * as well.</p> 463 * 464 * @hide 465 */ 466 @IntDef(prefix = { "COMMIT_REASON_" }, value = { 467 COMMIT_REASON_UNKNOWN, 468 COMMIT_REASON_ACTIVITY_FINISHED, 469 COMMIT_REASON_VIEW_COMMITTED, 470 COMMIT_REASON_VIEW_CLICKED, 471 COMMIT_REASON_VIEW_CHANGED, 472 COMMIT_REASON_SESSION_DESTROYED 473 }) 474 @Retention(RetentionPolicy.SOURCE) 475 public @interface AutofillCommitReason {} 476 477 /** 478 * Autofill context was committed because of an unknown reason. 479 * 480 * @hide 481 */ 482 public static final int COMMIT_REASON_UNKNOWN = 0; 483 484 /** 485 * Autofill context was committed because activity finished. 486 * 487 * @hide 488 */ 489 public static final int COMMIT_REASON_ACTIVITY_FINISHED = 1; 490 491 /** 492 * Autofill context was committed because {@link #commit()} was called. 493 * 494 * @hide 495 */ 496 public static final int COMMIT_REASON_VIEW_COMMITTED = 2; 497 498 /** 499 * Autofill context was committed because view was clicked. 500 * 501 * @hide 502 */ 503 public static final int COMMIT_REASON_VIEW_CLICKED = 3; 504 505 /** 506 * Autofill context was committed because of view changed. 507 * 508 * @hide 509 */ 510 public static final int COMMIT_REASON_VIEW_CHANGED = 4; 511 /** 512 * Autofill context was committed because of the session was destroyed. 513 * 514 * @hide 515 */ 516 public static final int COMMIT_REASON_SESSION_DESTROYED = 5; 517 518 /** 519 * Makes an authentication id from a request id and a dataset id. 520 * 521 * @param requestId The request id. 522 * @param datasetId The dataset id. 523 * @return The authentication id. 524 * @hide 525 */ makeAuthenticationId(int requestId, int datasetId)526 public static int makeAuthenticationId(int requestId, int datasetId) { 527 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT) 528 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK); 529 } 530 531 /** 532 * Gets the request id from an authentication id. 533 * 534 * @param authRequestId The authentication id. 535 * @return The request id. 536 * @hide 537 */ getRequestIdFromAuthenticationId(int authRequestId)538 public static int getRequestIdFromAuthenticationId(int authRequestId) { 539 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT); 540 } 541 542 /** 543 * Gets the dataset id from an authentication id. 544 * 545 * @param authRequestId The authentication id. 546 * @return The dataset id. 547 * @hide 548 */ getDatasetIdFromAuthenticationId(int authRequestId)549 public static int getDatasetIdFromAuthenticationId(int authRequestId) { 550 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK); 551 } 552 553 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 554 555 /** 556 * There is currently no session running. 557 * {@hide} 558 */ 559 public static final int NO_SESSION = Integer.MAX_VALUE; 560 561 private final IAutoFillManager mService; 562 563 private final Object mLock = new Object(); 564 565 @GuardedBy("mLock") 566 private IAutoFillManagerClient mServiceClient; 567 568 @GuardedBy("mLock") 569 private Cleaner mServiceClientCleaner; 570 571 @GuardedBy("mLock") 572 private IAugmentedAutofillManagerClient mAugmentedAutofillServiceClient; 573 574 @GuardedBy("mLock") 575 private AutofillCallback mCallback; 576 577 private final Context mContext; 578 579 @GuardedBy("mLock") 580 private int mSessionId = NO_SESSION; 581 582 @GuardedBy("mLock") 583 private int mState = STATE_UNKNOWN; 584 585 @GuardedBy("mLock") 586 private boolean mEnabled; 587 588 /** If a view changes to this mapping the autofill operation was successful */ 589 @GuardedBy("mLock") 590 @Nullable private ParcelableMap mLastAutofilledData; 591 592 /** If view tracking is enabled, contains the tracking state */ 593 @GuardedBy("mLock") 594 @Nullable private TrackedViews mTrackedViews; 595 596 /** Views that are only tracked because they are fillable and could be anchoring the UI. */ 597 @GuardedBy("mLock") 598 @Nullable private ArraySet<AutofillId> mFillableIds; 599 600 /** id of last requested autofill ui */ 601 @Nullable private AutofillId mIdShownFillUi; 602 603 /** 604 * Views that were already "entered" - if they're entered again when the session is not active, 605 * they're ignored 606 * */ 607 @GuardedBy("mLock") 608 @Nullable private ArraySet<AutofillId> mEnteredIds; 609 610 /** 611 * Views that were otherwised not important for autofill but triggered a session because the 612 * context is allowlisted for augmented autofill. 613 */ 614 @GuardedBy("mLock") 615 @Nullable private Set<AutofillId> mEnteredForAugmentedAutofillIds; 616 617 /** If set, session is commited when the field is clicked. */ 618 @GuardedBy("mLock") 619 @Nullable private AutofillId mSaveTriggerId; 620 621 /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ 622 @GuardedBy("mLock") 623 private boolean mOnInvisibleCalled; 624 625 /** If set, session is commited when the activity is finished; otherwise session is canceled. */ 626 @GuardedBy("mLock") 627 private boolean mSaveOnFinish; 628 629 /** If compatibility mode is enabled - this is a bridge to interact with a11y */ 630 @GuardedBy("mLock") 631 private CompatibilityBridge mCompatibilityBridge; 632 633 @Nullable 634 private final AutofillOptions mOptions; 635 636 /** When set, session is only used for augmented autofill requests. */ 637 @GuardedBy("mLock") 638 private boolean mForAugmentedAutofillOnly; 639 640 /** 641 * When set, standard autofill is disabled, but sessions can still be created for augmented 642 * autofill only. 643 */ 644 @GuardedBy("mLock") 645 private boolean mEnabledForAugmentedAutofillOnly; 646 647 private boolean mHasCredentialField; 648 649 /** 650 * Indicates whether there is already a field to do a fill request after 651 * the activity started. 652 * 653 * Autofill will automatically trigger a fill request after activity 654 * start if there is any field is autofillable. But if there is a field that 655 * triggered autofill, it is unnecessary to trigger again through 656 * AutofillManager#notifyViewEnteredForFillDialog. 657 */ 658 private AtomicBoolean mIsFillRequested; 659 660 @Nullable private List<AutofillId> mFillDialogTriggerIds; 661 662 private final boolean mIsFillDialogEnabled; 663 664 private final boolean mIsFillAndSaveDialogDisabledForCredentialManager; 665 666 // Indicate whether trigger fill request on unimportant views is enabled 667 private boolean mIsTriggerFillRequestOnUnimportantViewEnabled = false; 668 669 // Indicate whether to apply heuristic check on important views before trigger fill request 670 private boolean mIsTriggerFillRequestOnFilteredImportantViewsEnabled; 671 672 // Indicate whether to enable autofill for all view types 673 private boolean mShouldEnableAutofillOnAllViewTypes; 674 675 // A set containing all non-autofillable ime actions passed by flag 676 private Set<String> mNonAutofillableImeActionIdSet = new ArraySet<>(); 677 678 // If a package is fully denied, then all views that marked as not 679 // important for autofill will not trigger fill request 680 private boolean mIsPackageFullyDeniedForAutofill = false; 681 682 // If a package is partially denied, autofill manager will check whether 683 // current activity is in deny set to decide whether to trigger fill request 684 private boolean mIsPackagePartiallyDeniedForAutofill = false; 685 686 // A deny set read from device config 687 private Set<String> mDeniedActivitySet = new ArraySet<>(); 688 689 // If a package is fully allowed, all views in package will skip the heuristic check 690 private boolean mIsPackageFullyAllowedForAutofill = false; 691 692 // If a package is partially denied, autofill manager will check whether 693 // current activity is in allowed activity set. If it's allowed activity, then autofill manager 694 // will skip the heuristic check 695 private boolean mIsPackagePartiallyAllowedForAutofill = false; 696 697 // An allowed activity set read from device config 698 private Set<String> mAllowedActivitySet = new ArraySet<>(); 699 700 // Whether to enable multi-line check when checking whether view is autofillable 701 private boolean mShouldEnableMultilineFilter; 702 703 // Indicate whether should include all view with autofill type not none in assist structure 704 private boolean mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure; 705 706 // Indicate whether should include all view in assist structure 707 private boolean mShouldIncludeAllChildrenViewInAssistStructure; 708 709 // Indicates whether called the showAutofillDialog() method. 710 private boolean mShowAutofillDialogCalled = false; 711 712 private final String[] mFillDialogEnabledHints; 713 714 // Tracked all views that have appeared, including views that there are no 715 // dataset in responses. Used to avoid request pre-fill request again and again. 716 private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>(); 717 718 /** @hide */ 719 public interface AutofillClient { 720 /** 721 * Asks the client to start an authentication flow. 722 * 723 * @param authenticationId A unique id of the authentication operation. 724 * @param intent The authentication intent. 725 * @param fillInIntent The authentication fill-in intent. 726 */ autofillClientAuthenticate(int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline)727 void autofillClientAuthenticate(int authenticationId, IntentSender intent, 728 Intent fillInIntent, boolean authenticateInline); 729 730 /** 731 * Tells the client this manager has state to be reset. 732 */ autofillClientResetableStateAvailable()733 void autofillClientResetableStateAvailable(); 734 735 /** 736 * Request showing the autofill UI. 737 * 738 * @param anchor The real view the UI needs to anchor to. 739 * @param width The width of the fill UI content. 740 * @param height The height of the fill UI content. 741 * @param virtualBounds The bounds of the virtual decendant of the anchor. 742 * @param presenter The presenter that controls the fill UI window. 743 * @return Whether the UI was shown. 744 */ autofillClientRequestShowFillUi(@onNull View anchor, int width, int height, @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter)745 boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height, 746 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter); 747 748 /** 749 * Dispatch unhandled keyevent from Autofill window 750 * @param anchor The real view the UI needs to anchor to. 751 * @param keyEvent Unhandled KeyEvent from autofill window. 752 */ autofillClientDispatchUnhandledKey(@onNull View anchor, @NonNull KeyEvent keyEvent)753 void autofillClientDispatchUnhandledKey(@NonNull View anchor, @NonNull KeyEvent keyEvent); 754 755 /** 756 * Request hiding the autofill UI. 757 * 758 * @return Whether the UI was hidden. 759 */ autofillClientRequestHideFillUi()760 boolean autofillClientRequestHideFillUi(); 761 762 /** 763 * Gets whether the fill UI is currenlty being shown. 764 * 765 * @return Whether the fill UI is currently being shown 766 */ autofillClientIsFillUiShowing()767 boolean autofillClientIsFillUiShowing(); 768 769 /** 770 * Checks if views are currently attached and visible. 771 * 772 * @return And array with {@code true} iff the view is attached or visible 773 */ autofillClientGetViewVisibility(@onNull AutofillId[] autofillIds)774 @NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds); 775 776 /** 777 * Checks is the client is currently visible as understood by autofill. 778 * 779 * @return {@code true} if the client is currently visible 780 */ autofillClientIsVisibleForAutofill()781 boolean autofillClientIsVisibleForAutofill(); 782 783 /** 784 * Client might disable enter/exit event e.g. when activity is paused. 785 */ isDisablingEnterExitEventForAutofill()786 boolean isDisablingEnterExitEventForAutofill(); 787 788 /** 789 * Finds views by traversing the hierarchies of the client. 790 * 791 * @param autofillIds The autofill ids of the views to find 792 * 793 * @return And array containing the views (empty if no views found). 794 */ autofillClientFindViewsByAutofillIdTraversal( @onNull AutofillId[] autofillIds)795 @NonNull View[] autofillClientFindViewsByAutofillIdTraversal( 796 @NonNull AutofillId[] autofillIds); 797 798 /** 799 * Finds a view by traversing the hierarchies of the client. 800 * 801 * @param autofillId The autofill id of the views to find 802 * 803 * @return The view, or {@code null} if not found 804 */ autofillClientFindViewByAutofillIdTraversal(@onNull AutofillId autofillId)805 @Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId); 806 807 /** 808 * Finds a view by a11y id in a given client window. 809 * 810 * @param viewId The accessibility id of the views to find 811 * @param windowId The accessibility window id where to search 812 * 813 * @return The view, or {@code null} if not found 814 */ autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId)815 @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId); 816 817 /** 818 * Runs the specified action on the UI thread. 819 */ autofillClientRunOnUiThread(Runnable action)820 void autofillClientRunOnUiThread(Runnable action); 821 822 /** 823 * Gets the complete component name of this client. 824 */ autofillClientGetComponentName()825 ComponentName autofillClientGetComponentName(); 826 827 /** 828 * Gets the activity token 829 */ autofillClientGetActivityToken()830 @Nullable IBinder autofillClientGetActivityToken(); 831 832 /** 833 * @return Whether compatibility mode is enabled. 834 */ autofillClientIsCompatibilityModeEnabled()835 boolean autofillClientIsCompatibilityModeEnabled(); 836 837 /** 838 * Gets the next unique autofill ID. 839 * 840 * <p>Typically used to manage views whose content is recycled - see 841 * {@link View#setAutofillId(AutofillId)} for more info. 842 * 843 * @return An ID that is unique in the activity. 844 */ autofillClientGetNextAutofillId()845 @Nullable AutofillId autofillClientGetNextAutofillId(); 846 } 847 848 /** 849 * @hide 850 */ AutofillManager(Context context, IAutoFillManager service)851 public AutofillManager(Context context, IAutoFillManager service) { 852 mContext = Objects.requireNonNull(context, "context cannot be null"); 853 mService = service; 854 mOptions = context.getAutofillOptions(); 855 mIsFillRequested = new AtomicBoolean(false); 856 857 mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled(); 858 mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints(); 859 860 mIsFillAndSaveDialogDisabledForCredentialManager = 861 AutofillFeatureFlags.isFillAndSaveDialogDisabledForCredentialManager(); 862 863 if (sDebug) { 864 Log.d(TAG, "Fill dialog is enabled:" + mIsFillDialogEnabled 865 + ", hints=" + Arrays.toString(mFillDialogEnabledHints)); 866 } 867 868 if (mOptions != null) { 869 sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0; 870 sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0; 871 } 872 873 mIsTriggerFillRequestOnUnimportantViewEnabled = 874 AutofillFeatureFlags.isTriggerFillRequestOnUnimportantViewEnabled(); 875 876 mIsTriggerFillRequestOnFilteredImportantViewsEnabled = 877 AutofillFeatureFlags.isTriggerFillRequestOnFilteredImportantViewsEnabled(); 878 879 mShouldEnableAutofillOnAllViewTypes = 880 AutofillFeatureFlags.shouldEnableAutofillOnAllViewTypes(); 881 882 mNonAutofillableImeActionIdSet = 883 AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag(); 884 885 mShouldEnableMultilineFilter = 886 AutofillFeatureFlags.shouldEnableMultilineFilter(); 887 888 final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag(); 889 final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag(); 890 891 final String packageName = mContext.getPackageName(); 892 893 mIsPackageFullyDeniedForAutofill = 894 isPackageFullyAllowedOrDeniedForAutofill(denyListString, packageName); 895 896 mIsPackageFullyAllowedForAutofill = 897 isPackageFullyAllowedOrDeniedForAutofill(allowlistString, packageName); 898 899 if (!mIsPackageFullyDeniedForAutofill) { 900 mIsPackagePartiallyDeniedForAutofill = 901 isPackagePartiallyDeniedOrAllowedForAutofill(denyListString, packageName); 902 } 903 904 if (!mIsPackageFullyAllowedForAutofill) { 905 mIsPackagePartiallyAllowedForAutofill = 906 isPackagePartiallyDeniedOrAllowedForAutofill(allowlistString, packageName); 907 } 908 909 if (mIsPackagePartiallyDeniedForAutofill) { 910 mDeniedActivitySet = getDeniedOrAllowedActivitySetFromString( 911 denyListString, packageName); 912 } 913 914 if (mIsPackagePartiallyAllowedForAutofill) { 915 mAllowedActivitySet = getDeniedOrAllowedActivitySetFromString( 916 allowlistString, packageName); 917 } 918 919 mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure 920 = AutofillFeatureFlags.shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue(); 921 922 mShouldIncludeAllChildrenViewInAssistStructure 923 = AutofillFeatureFlags.shouldIncludeAllChildrenViewInAssistStructure(); 924 } 925 926 /** 927 * Whether to apply heuristic check on important views before triggering fill request 928 * 929 * @hide 930 */ isTriggerFillRequestOnFilteredImportantViewsEnabled()931 public boolean isTriggerFillRequestOnFilteredImportantViewsEnabled() { 932 return mIsTriggerFillRequestOnFilteredImportantViewsEnabled; 933 } 934 935 /** 936 * Whether to trigger fill request on not important views that passes heuristic check 937 * 938 * @hide 939 */ isTriggerFillRequestOnUnimportantViewEnabled()940 public boolean isTriggerFillRequestOnUnimportantViewEnabled() { 941 return mIsTriggerFillRequestOnUnimportantViewEnabled; 942 } 943 944 /** 945 * Whether view passes the imeAction check 946 * 947 */ isPassingImeActionCheck(EditText editText)948 private boolean isPassingImeActionCheck(EditText editText) { 949 final int actionId = editText.getImeOptions(); 950 if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) { 951 Log.d(TAG, "view not autofillable - not passing ime action check"); 952 return false; 953 } 954 return true; 955 } 956 957 /** 958 * Checks whether the view passed in is not multiline text 959 * 960 * @param editText the view that passed to this check 961 * @return true if the view input is not multiline, false otherwise 962 */ isPassingMultilineCheck(EditText editText)963 private boolean isPassingMultilineCheck(EditText editText) { 964 // check if min line is set to be greater than 1 965 if (editText.getMinLines() > 1) { 966 Log.d(TAG, "view not autofillable - has multiline input type"); 967 return false; 968 } 969 return true; 970 } 971 isPackageFullyAllowedOrDeniedForAutofill( @onNull String listString, @NonNull String packageName)972 private boolean isPackageFullyAllowedOrDeniedForAutofill( 973 @NonNull String listString, @NonNull String packageName) { 974 // If "PackageName:;" is in the string, then it the package is fully denied or allowed for 975 // autofill, depending on which string is passed to this function 976 return listString.indexOf(packageName + ":;") != -1; 977 } 978 isPackagePartiallyDeniedOrAllowedForAutofill( @onNull String listString, @NonNull String packageName)979 private boolean isPackagePartiallyDeniedOrAllowedForAutofill( 980 @NonNull String listString, @NonNull String packageName) { 981 // If "PackageName:" is in string when "PackageName:;" is not, then it means there are 982 // specific activities to be allowed or denied. So the package is partially allowed or 983 // denied for autofill. 984 return listString.indexOf(packageName + ":") != -1; 985 } 986 987 /** 988 * @hide 989 */ shouldIncludeAllChildrenViewsWithAutofillTypeNotNoneInAssistStructure()990 public boolean shouldIncludeAllChildrenViewsWithAutofillTypeNotNoneInAssistStructure() { 991 return mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure; 992 } 993 994 /** 995 * @hide 996 */ shouldIncludeAllChildrenViewInAssistStructure()997 public boolean shouldIncludeAllChildrenViewInAssistStructure() { 998 return mShouldIncludeAllChildrenViewInAssistStructure; 999 } 1000 1001 /** 1002 * Get the denied or allowed activitiy names under specified package from the list string and 1003 * set it in fields accordingly 1004 * 1005 * For example, if the package name is Package1, and the string is 1006 * "Package1:Activity1,Activity2;", then the extracted activity set would be 1007 * {Activity1, Activity2} 1008 * 1009 * @param listString Denylist that is got from device config. For example, 1010 * "Package1:Activity1,Activity2;Package2:;" 1011 * @param packageName Specify which package to extract.For example, "Package1" 1012 * 1013 * @return the extracted activity set, For example, {Activity1, Activity2} 1014 */ getDeniedOrAllowedActivitySetFromString( @onNull String listString, @NonNull String packageName)1015 private Set<String> getDeniedOrAllowedActivitySetFromString( 1016 @NonNull String listString, @NonNull String packageName) { 1017 // 1. Get the index of where the Package name starts 1018 final int packageInStringIndex = listString.indexOf(packageName + ":"); 1019 1020 // 2. Get the ";" index after this index of package 1021 final int firstNextSemicolonIndex = listString.indexOf(";", packageInStringIndex); 1022 1023 // 3. Get the activity names substring between the indexes 1024 final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1; 1025 1026 if (activityStringStartIndex >= firstNextSemicolonIndex) { 1027 Log.e(TAG, "Failed to get denied activity names from list because it's wrongly " 1028 + "formatted"); 1029 return new ArraySet<>(); 1030 } 1031 final String activitySubstring = 1032 listString.substring(activityStringStartIndex, firstNextSemicolonIndex); 1033 1034 // 4. Split the activity name substring 1035 final String[] activityStringArray = activitySubstring.split(","); 1036 1037 // 5. return the extracted activities in a set 1038 return new ArraySet<>(Arrays.asList(activityStringArray)); 1039 } 1040 1041 /** 1042 * Check whether autofill is denied for current activity or package. If current activity or 1043 * package is denied, then the view won't trigger fill request. 1044 * 1045 * @hide 1046 */ isActivityDeniedForAutofill()1047 public boolean isActivityDeniedForAutofill() { 1048 if (mIsPackageFullyDeniedForAutofill) { 1049 return true; 1050 } 1051 if (mIsPackagePartiallyDeniedForAutofill) { 1052 final AutofillClient client = getClient(); 1053 if (client == null) { 1054 return false; 1055 } 1056 final ComponentName clientActivity = client.autofillClientGetComponentName(); 1057 if (mDeniedActivitySet.contains(clientActivity.flattenToShortString())) { 1058 return true; 1059 } 1060 } 1061 return false; 1062 } 1063 1064 /** 1065 * Check whether current activity is allowlisted for autofill. 1066 * 1067 * If it is, the view in current activity will bypass heuristic check when checking whether it's 1068 * autofillable 1069 * 1070 * @hide 1071 */ isActivityAllowedForAutofill()1072 public boolean isActivityAllowedForAutofill() { 1073 if (mIsPackageFullyAllowedForAutofill) { 1074 return true; 1075 } 1076 if (mIsPackagePartiallyAllowedForAutofill) { 1077 final AutofillClient client = getClient(); 1078 if (client == null) { 1079 return false; 1080 } 1081 final ComponentName clientActivity = client.autofillClientGetComponentName(); 1082 if (mAllowedActivitySet.contains(clientActivity.flattenToShortString())) { 1083 return true; 1084 } 1085 } 1086 return false; 1087 } 1088 1089 /** 1090 * Check heuristics and other rules to determine if view is autofillable 1091 * 1092 * Note: this function should be only called only when autofill for all apps is turned on. The 1093 * calling method needs to check the corresponding flag to make sure that before calling into 1094 * this function. 1095 * 1096 * @hide 1097 */ isAutofillable(View view)1098 public boolean isAutofillable(View view) { 1099 // Duplicate the autofill type check here because ViewGroup will call this function to 1100 // decide whether to include view in assist structure. 1101 // Also keep the autofill type check inside View#IsAutofillable() to serve as an early out 1102 // or if other functions need to call it. 1103 if (view.getAutofillType() == View.AUTOFILL_TYPE_NONE) return false; 1104 1105 // denylist only applies to not important views 1106 if (!view.isImportantForAutofill() && isActivityDeniedForAutofill()) { 1107 Log.d(TAG, "view is not autofillable - activity denied for autofill"); 1108 return false; 1109 } 1110 1111 if (isActivityAllowedForAutofill()) { 1112 Log.d(TAG, "view is autofillable - activity allowed for autofill"); 1113 return true; 1114 } 1115 1116 if (view instanceof EditText) { 1117 if (mShouldEnableMultilineFilter && !isPassingMultilineCheck((EditText) view)) { 1118 return false; 1119 } 1120 return isPassingImeActionCheck((EditText) view); 1121 } 1122 1123 // Skip view type check if view is important for autofill or 1124 // shouldEnableAutofillOnAllViewTypes flag is turned on 1125 if (view.isImportantForAutofill() || mShouldEnableAutofillOnAllViewTypes) { 1126 return true; 1127 } 1128 1129 if (view instanceof CheckBox 1130 || view instanceof Spinner 1131 || view instanceof DatePicker 1132 || view instanceof TimePicker 1133 || view instanceof RadioGroup) { 1134 return true; 1135 } 1136 Log.d(TAG, "view is not autofillable - not important and filtered by view type check"); 1137 return false; 1138 } 1139 1140 /** 1141 * @hide 1142 */ enableCompatibilityMode()1143 public void enableCompatibilityMode() { 1144 synchronized (mLock) { 1145 // The accessibility manager is a singleton so we may need to plug 1146 // different bridge based on which activity is currently focused 1147 // in the current process. Since compat would be rarely used, just 1148 // create and register a new instance every time. 1149 if (sDebug) { 1150 Slog.d(TAG, "creating CompatibilityBridge for " + mContext); 1151 } 1152 mCompatibilityBridge = new CompatibilityBridge(); 1153 } 1154 } 1155 1156 /** 1157 * Restore state after activity lifecycle 1158 * 1159 * @param savedInstanceState The state to be restored 1160 * 1161 * {@hide} 1162 */ onCreate(Bundle savedInstanceState)1163 public void onCreate(Bundle savedInstanceState) { 1164 if (!hasAutofillFeature()) { 1165 return; 1166 } 1167 synchronized (mLock) { 1168 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG, android.view.autofill.ParcelableMap.class); 1169 1170 if (isActiveLocked()) { 1171 Log.w(TAG, "New session was started before onCreate()"); 1172 return; 1173 } 1174 1175 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); 1176 mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); 1177 1178 if (mSessionId != NO_SESSION) { 1179 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1180 1181 final AutofillClient client = getClient(); 1182 if (client != null) { 1183 final SyncResultReceiver receiver = new SyncResultReceiver( 1184 SYNC_CALLS_TIMEOUT_MS); 1185 try { 1186 boolean sessionWasRestored = false; 1187 if (clientAdded) { 1188 mService.restoreSession(mSessionId, 1189 client.autofillClientGetActivityToken(), 1190 mServiceClient.asBinder(), receiver); 1191 sessionWasRestored = receiver.getIntResult() == 1; 1192 } else { 1193 Log.w(TAG, "No service client for session " + mSessionId); 1194 } 1195 1196 if (!sessionWasRestored) { 1197 Log.w(TAG, "Session " + mSessionId + " could not be restored"); 1198 mSessionId = NO_SESSION; 1199 mState = STATE_UNKNOWN; 1200 } else { 1201 if (sDebug) { 1202 Log.d(TAG, "session " + mSessionId + " was restored"); 1203 } 1204 1205 client.autofillClientResetableStateAvailable(); 1206 } 1207 } catch (RemoteException e) { 1208 Log.e(TAG, "Could not figure out if there was an autofill session", e); 1209 } catch (SyncResultReceiver.TimeoutException e) { 1210 Log.e(TAG, "Fail to get session restore status: " + e); 1211 } 1212 } 1213 } 1214 } 1215 } 1216 1217 /** 1218 * Called once the client becomes visible. 1219 * 1220 * @see AutofillClient#autofillClientIsVisibleForAutofill() 1221 * 1222 * {@hide} 1223 */ onVisibleForAutofill()1224 public void onVisibleForAutofill() { 1225 // This gets called when the client just got visible at which point the visibility 1226 // of the tracked views may not have been computed (due to a pending layout, etc). 1227 // While generally we have no way to know when the UI has settled. We will evaluate 1228 // the tracked views state at the end of next frame to guarantee that everything 1229 // that may need to be laid out is laid out. 1230 Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { 1231 synchronized (mLock) { 1232 if (mEnabled && isActiveLocked() && mTrackedViews != null) { 1233 mTrackedViews.onVisibleForAutofillChangedLocked(); 1234 } 1235 } 1236 }, null); 1237 } 1238 1239 /** 1240 * Called once the client becomes invisible. 1241 * 1242 * @see AutofillClient#autofillClientIsVisibleForAutofill() 1243 * 1244 * @param isExpiredResponse The response has expired or not 1245 * 1246 * {@hide} 1247 */ onInvisibleForAutofill(boolean isExpiredResponse)1248 public void onInvisibleForAutofill(boolean isExpiredResponse) { 1249 synchronized (mLock) { 1250 mOnInvisibleCalled = true; 1251 1252 if (isExpiredResponse) { 1253 // Notify service the response has expired. 1254 updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null, 1255 ACTION_RESPONSE_EXPIRED, /* flags= */ 0); 1256 } 1257 } 1258 } 1259 1260 /** 1261 * Save state before activity lifecycle 1262 * 1263 * @param outState Place to store the state 1264 * 1265 * {@hide} 1266 */ onSaveInstanceState(Bundle outState)1267 public void onSaveInstanceState(Bundle outState) { 1268 if (!hasAutofillFeature()) { 1269 return; 1270 } 1271 synchronized (mLock) { 1272 if (mSessionId != NO_SESSION) { 1273 outState.putInt(SESSION_ID_TAG, mSessionId); 1274 } 1275 if (mState != STATE_UNKNOWN) { 1276 outState.putInt(STATE_TAG, mState); 1277 } 1278 if (mLastAutofilledData != null) { 1279 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); 1280 } 1281 } 1282 } 1283 1284 /** 1285 * @hide 1286 */ 1287 @GuardedBy("mLock") isCompatibilityModeEnabledLocked()1288 public boolean isCompatibilityModeEnabledLocked() { 1289 return mCompatibilityBridge != null; 1290 } 1291 1292 /** 1293 * Checks whether autofill is enabled for the current user. 1294 * 1295 * <p>Typically used to determine whether the option to explicitly request autofill should 1296 * be offered - see {@link #requestAutofill(View)}. 1297 * 1298 * @return whether autofill is enabled for the current user. 1299 */ isEnabled()1300 public boolean isEnabled() { 1301 if (!hasAutofillFeature()) { 1302 return false; 1303 } 1304 synchronized (mLock) { 1305 if (isDisabledByServiceLocked()) { 1306 return false; 1307 } 1308 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1309 return clientAdded ? mEnabled : false; 1310 } 1311 } 1312 1313 /** 1314 * Should always be called from {@link AutofillService#getFillEventHistory()}. 1315 * 1316 * @hide 1317 */ getFillEventHistory()1318 @Nullable public FillEventHistory getFillEventHistory() { 1319 try { 1320 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 1321 mService.getFillEventHistory(receiver); 1322 return receiver.getParcelableResult(); 1323 } catch (RemoteException e) { 1324 throw e.rethrowFromSystemServer(); 1325 } catch (SyncResultReceiver.TimeoutException e) { 1326 Log.e(TAG, "Fail to get fill event history: " + e); 1327 return null; 1328 } 1329 } 1330 1331 /** 1332 * Explicitly requests a new autofill context. 1333 * 1334 * <p>Normally, the autofill context is automatically started if necessary when 1335 * {@link #notifyViewEntered(View)} is called, but this method should be used in the 1336 * cases where it must be explicitly started. For example, when the view offers an AUTOFILL 1337 * option on its contextual overflow menu, and the user selects it. 1338 * 1339 * @param view view requesting the new autofill context. 1340 */ requestAutofill(@onNull View view)1341 public void requestAutofill(@NonNull View view) { 1342 int flags = FLAG_MANUAL_REQUEST; 1343 if (!view.isFocused()) { 1344 flags |= FLAG_VIEW_NOT_FOCUSED; 1345 } 1346 notifyViewEntered(view, flags); 1347 } 1348 1349 /** 1350 * Explicitly cancels the current session and requests a new autofill context. 1351 * 1352 * <p>Normally, the autofill context is automatically started if necessary when 1353 * {@link #notifyViewEntered(View)} is called, but this method should be used in 1354 * cases where it must be explicitly started or restarted. Currently, this method should only 1355 * be called by 1356 * {@link android.service.autofill.augmented.AugmentedAutofillService#requestAutofill( 1357 * ComponentName, AutofillId)} to cancel the current session and trigger the autofill flow in 1358 * a new session, giving the autofill service or the augmented autofill service a chance to 1359 * send updated suggestions. 1360 * 1361 * @param view view requesting the new autofill context. 1362 */ requestAutofillFromNewSession(@onNull View view)1363 void requestAutofillFromNewSession(@NonNull View view) { 1364 cancel(); 1365 notifyViewEntered(view); 1366 } 1367 1368 /** 1369 * Explicitly requests a new autofill context for virtual views. 1370 * 1371 * <p>Normally, the autofill context is automatically started if necessary when 1372 * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the 1373 * cases where it must be explicitly started. For example, when the virtual view offers an 1374 * AUTOFILL option on its contextual overflow menu, and the user selects it. 1375 * 1376 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 1377 * parent view uses {@code bounds} to draw the virtual view inside its Canvas, 1378 * the absolute bounds could be calculated by: 1379 * 1380 * <pre class="prettyprint"> 1381 * int offset[] = new int[2]; 1382 * getLocationOnScreen(offset); 1383 * Rect absBounds = new Rect(bounds.left + offset[0], 1384 * bounds.top + offset[1], 1385 * bounds.right + offset[0], bounds.bottom + offset[1]); 1386 * </pre> 1387 * 1388 * @param view the virtual view parent. 1389 * @param virtualId id identifying the virtual child inside the parent view. 1390 * @param absBounds absolute boundaries of the virtual view in the screen. 1391 */ requestAutofill(@onNull View view, int virtualId, @NonNull Rect absBounds)1392 public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 1393 int flags = FLAG_MANUAL_REQUEST; 1394 if (!view.isFocused()) { 1395 flags |= FLAG_VIEW_NOT_FOCUSED; 1396 } 1397 notifyViewEntered(view, virtualId, absBounds, flags); 1398 } 1399 1400 /** 1401 * Called when a {@link View} that supports autofill is entered. 1402 * 1403 * @param view {@link View} that was entered. 1404 */ notifyViewEntered(@onNull View view)1405 public void notifyViewEntered(@NonNull View view) { 1406 notifyViewEntered(view, 0); 1407 } 1408 1409 /** 1410 * Called when the virtual views are ready to the user for autofill. 1411 * 1412 * This method is used to notify autofill system the views are ready to the user. And then 1413 * Autofill can do initialization if needed before the user starts to input. For example, do 1414 * a pre-fill request for the 1415 * <a href="/reference/android/service/autofill/Dataset.html#FillDialogUI">fill dialog</a>. 1416 * 1417 * @param view the host view that holds a virtual view hierarchy. 1418 * @param infos extra information for the virtual views. The key is virtual id which represents 1419 * the virtual view in the host view. 1420 * 1421 * @throws IllegalArgumentException if the {@code infos} was empty 1422 */ notifyVirtualViewsReady( @onNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos)1423 public void notifyVirtualViewsReady( 1424 @NonNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos) { 1425 Objects.requireNonNull(infos); 1426 if (infos.size() == 0) { 1427 throw new IllegalArgumentException("No VirtualViewInfo found"); 1428 } 1429 if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) { 1430 if (sDebug) { 1431 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" 1432 + view.getAutofillId().toString()); 1433 } 1434 mHasCredentialField = true; 1435 return; 1436 } 1437 for (int i = 0; i < infos.size(); i++) { 1438 final VirtualViewFillInfo info = infos.valueAt(i); 1439 final int virtualId = infos.keyAt(i); 1440 notifyViewReadyInner(getAutofillId(view, virtualId), 1441 (info == null) ? null : info.getAutofillHints()); 1442 } 1443 } 1444 1445 /** 1446 * The {@link AutofillFeatureFlags#DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or 1447 * the view have the allowed autofill hints, performs a fill request to know there is any field 1448 * supported fill dialog. 1449 * 1450 * @hide 1451 */ notifyViewEnteredForFillDialog(View v)1452 public void notifyViewEnteredForFillDialog(View v) { 1453 if (v.isCredential() 1454 && mIsFillAndSaveDialogDisabledForCredentialManager) { 1455 if (sDebug) { 1456 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" 1457 + v.getAutofillId()); 1458 } 1459 mHasCredentialField = true; 1460 return; 1461 } 1462 notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints()); 1463 } 1464 notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints)1465 private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints) { 1466 if (sDebug) { 1467 Log.d(TAG, "notifyViewReadyInner:" + id); 1468 } 1469 1470 if (!hasAutofillFeature()) { 1471 return; 1472 } 1473 synchronized (mLock) { 1474 if (mAllTrackedViews.contains(id)) { 1475 // The id is tracked and will not trigger pre-fill request again. 1476 return; 1477 } 1478 1479 // Add the id as tracked to avoid triggering fill request again and again. 1480 mAllTrackedViews.add(id); 1481 if (mTrackedViews != null) { 1482 // To support the fill dialog can show for the autofillable Views in 1483 // different pages but in the same Activity. We need to reset the 1484 // mIsFillRequested flag to allow asking for a new FillRequest when 1485 // user switches to other page 1486 mTrackedViews.checkViewState(id); 1487 } 1488 } 1489 1490 // Skip if the fill request has been performed for a view. 1491 if (mIsFillRequested.get()) { 1492 return; 1493 } 1494 1495 // Start session with PCC flag to get assist structure and send field classification request 1496 // to PCC classification service. 1497 if (AutofillFeatureFlags.isAutofillPccClassificationEnabled()) { 1498 synchronized (mLock) { 1499 // If session has already been created, that'd mean we already have issued the 1500 // detection request previously. It is possible in cases like autofocus that this 1501 // method isn't invoked, so the server should still handle such cases where fill 1502 // request comes in but PCC Detection hasn't been triggered. There is no benefit to 1503 // trigger PCC Detection separately in those cases. 1504 if (!isActiveLocked()) { 1505 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1506 if (clientAdded) { 1507 startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null, 1508 /* value= */ null, /* flags= */ FLAG_PCC_DETECTION); 1509 } else { 1510 if (sVerbose) { 1511 Log.v(TAG, "not starting session: no service client"); 1512 } 1513 } 1514 } 1515 } 1516 } 1517 1518 // Check if framework should send pre-fill request for fill dialog 1519 boolean shouldSendPreFillRequestForFillDialog = false; 1520 if (mIsFillDialogEnabled) { 1521 shouldSendPreFillRequestForFillDialog = true; 1522 } else if (autofillHints != null) { 1523 // check if supported autofill hint is present 1524 for (String autofillHint : autofillHints) { 1525 for (String filldialogEnabledHint : mFillDialogEnabledHints) { 1526 if (filldialogEnabledHint.equalsIgnoreCase(autofillHint)) { 1527 shouldSendPreFillRequestForFillDialog = true; 1528 break; 1529 } 1530 } 1531 if (shouldSendPreFillRequestForFillDialog) break; 1532 } 1533 } 1534 if (shouldSendPreFillRequestForFillDialog) { 1535 if (sDebug) { 1536 Log.d(TAG, "Triggering pre-emptive request for fill dialog."); 1537 } 1538 int flags = FLAG_SUPPORTS_FILL_DIALOG; 1539 flags |= FLAG_VIEW_NOT_FOCUSED; 1540 synchronized (mLock) { 1541 // To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill 1542 // request, because IME will reset the id of IME served view to 0 when activity 1543 // start and does not focus on any view. If the id of the prefill request does 1544 // not match the IME served view's, Autofill will be blocking to wait inline 1545 // request from the IME. 1546 notifyViewEnteredLocked(/* view= */ null, AutofillId.NO_AUTOFILL_ID, 1547 /* bounds= */ null, /* value= */ null, flags); 1548 } 1549 } 1550 return; 1551 } 1552 hasFillDialogUiFeature()1553 private boolean hasFillDialogUiFeature() { 1554 return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints); 1555 } 1556 getImeStateFlag(View v)1557 private int getImeStateFlag(View v) { 1558 if (v == null) return 0; 1559 1560 final WindowInsets rootWindowInsets = v.getRootWindowInsets(); 1561 if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) { 1562 return FLAG_IME_SHOWING; 1563 } 1564 return 0; 1565 } 1566 1567 @GuardedBy("mLock") shouldIgnoreViewEnteredLocked(@onNull AutofillId id, int flags)1568 private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) { 1569 if (isDisabledByServiceLocked()) { 1570 if (sVerbose) { 1571 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 1572 + ") on state " + getStateAsStringLocked() + " because disabled by svc"); 1573 } 1574 return true; 1575 } 1576 if (isFinishedLocked()) { 1577 // Session already finished: ignore if automatic request and view already entered 1578 if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null 1579 && mEnteredIds.contains(id)) { 1580 if (sVerbose) { 1581 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 1582 + ") on state " + getStateAsStringLocked() 1583 + " because view was already entered: " + mEnteredIds); 1584 } 1585 return true; 1586 } 1587 } 1588 return false; 1589 } 1590 isClientVisibleForAutofillLocked()1591 private boolean isClientVisibleForAutofillLocked() { 1592 final AutofillClient client = getClient(); 1593 return client != null && client.autofillClientIsVisibleForAutofill(); 1594 } 1595 isClientDisablingEnterExitEvent()1596 private boolean isClientDisablingEnterExitEvent() { 1597 final AutofillClient client = getClient(); 1598 return client != null && client.isDisablingEnterExitEventForAutofill(); 1599 } 1600 notifyViewEntered(@onNull View view, int flags)1601 private void notifyViewEntered(@NonNull View view, int flags) { 1602 if (!hasAutofillFeature()) { 1603 return; 1604 } 1605 AutofillCallback callback; 1606 synchronized (mLock) { 1607 callback = notifyViewEnteredLocked( 1608 view, view.getAutofillId(), /* bounds= */ null, view.getAutofillValue(), flags); 1609 } 1610 1611 if (callback != null) { 1612 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1613 } 1614 } 1615 1616 /** 1617 * Called when a {@link View} that supports autofill is exited. 1618 * 1619 * @param view {@link View} that was exited. 1620 */ notifyViewExited(@onNull View view)1621 public void notifyViewExited(@NonNull View view) { 1622 if (!hasAutofillFeature()) { 1623 return; 1624 } 1625 synchronized (mLock) { 1626 notifyViewExitedLocked(view); 1627 } 1628 } 1629 1630 @GuardedBy("mLock") notifyViewExitedLocked(@onNull View view)1631 void notifyViewExitedLocked(@NonNull View view) { 1632 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1633 1634 if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) 1635 && isActiveLocked()) { 1636 // dont notify exited when Activity is already in background 1637 if (!isClientDisablingEnterExitEvent()) { 1638 final AutofillId id = view.getAutofillId(); 1639 1640 // Update focus on existing session. 1641 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 1642 } 1643 } 1644 } 1645 1646 /** 1647 * Called when a {@link View view's} visibility changed. 1648 * 1649 * @param view {@link View} that was exited. 1650 * @param isVisible visible if the view is visible in the view hierarchy. 1651 */ notifyViewVisibilityChanged(@onNull View view, boolean isVisible)1652 public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) { 1653 notifyViewVisibilityChangedInternal(view, 0, isVisible, false); 1654 } 1655 1656 /** 1657 * Called when a virtual view's visibility changed. 1658 * 1659 * @param view {@link View} that was exited. 1660 * @param virtualId id identifying the virtual child inside the parent view. 1661 * @param isVisible visible if the view is visible in the view hierarchy. 1662 */ notifyViewVisibilityChanged(@onNull View view, int virtualId, boolean isVisible)1663 public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) { 1664 notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true); 1665 } 1666 1667 /** 1668 * Called when a view/virtual view's visibility changed. 1669 * 1670 * @param view {@link View} that was exited. 1671 * @param virtualId id identifying the virtual child inside the parent view. 1672 * @param isVisible visible if the view is visible in the view hierarchy. 1673 * @param virtual Whether the view is virtual. 1674 */ notifyViewVisibilityChangedInternal(@onNull View view, int virtualId, boolean isVisible, boolean virtual)1675 private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId, 1676 boolean isVisible, boolean virtual) { 1677 synchronized (mLock) { 1678 if (mForAugmentedAutofillOnly) { 1679 if (sVerbose) { 1680 Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode"); 1681 } 1682 return; 1683 } 1684 if (mEnabled && isActiveLocked()) { 1685 final AutofillId id = virtual ? getAutofillId(view, virtualId) 1686 : view.getAutofillId(); 1687 if (sVerbose) Log.v(TAG, "visibility changed for " + id + ": " + isVisible); 1688 if (!isVisible && mFillableIds != null) { 1689 if (mFillableIds.contains(id)) { 1690 if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible"); 1691 requestHideFillUi(id, view); 1692 } 1693 } 1694 if (mTrackedViews != null) { 1695 mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); 1696 } else if (sVerbose) { 1697 Log.v(TAG, "Ignoring visibility change on " + id + ": no tracked views"); 1698 } 1699 } else if (!virtual && isVisible) { 1700 startAutofillIfNeededLocked(view); 1701 } 1702 } 1703 } 1704 1705 /** 1706 * Called when a virtual view that supports autofill is entered. 1707 * 1708 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 1709 * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas, 1710 * the absolute bounds could be calculated by: 1711 * 1712 * <pre class="prettyprint"> 1713 * int offset[] = new int[2]; 1714 * getLocationOnScreen(offset); 1715 * Rect absBounds = new Rect(bounds.left + offset[0], 1716 * bounds.top + offset[1], 1717 * bounds.right + offset[0], bounds.bottom + offset[1]); 1718 * </pre> 1719 * 1720 * @param view the virtual view parent. 1721 * @param virtualId id identifying the virtual child inside the parent view. 1722 * @param absBounds absolute boundaries of the virtual view in the screen. 1723 */ notifyViewEntered(@onNull View view, int virtualId, @NonNull Rect absBounds)1724 public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 1725 notifyViewEntered(view, virtualId, absBounds, 0); 1726 } 1727 notifyViewEntered(View view, int virtualId, Rect bounds, int flags)1728 private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) { 1729 if (!hasAutofillFeature()) { 1730 return; 1731 } 1732 1733 AutofillCallback callback; 1734 synchronized (mLock) { 1735 callback = notifyViewEnteredLocked( 1736 view, getAutofillId(view, virtualId), bounds, /* value= */ null, flags); 1737 } 1738 1739 if (callback != null) { 1740 callback.onAutofillEvent(view, virtualId, 1741 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1742 } 1743 } 1744 1745 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ 1746 @GuardedBy("mLock") notifyViewEnteredLocked(@ullable View view, AutofillId id, Rect bounds, AutofillValue value, int flags)1747 private AutofillCallback notifyViewEnteredLocked(@Nullable View view, AutofillId id, 1748 Rect bounds, AutofillValue value, int flags) { 1749 if (shouldIgnoreViewEnteredLocked(id, flags)) return null; 1750 1751 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1752 if (!clientAdded) { 1753 if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); 1754 return null; 1755 } 1756 1757 if (!mEnabled && !mEnabledForAugmentedAutofillOnly) { 1758 if (sVerbose) { 1759 Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); 1760 } 1761 return mCallback; 1762 } 1763 1764 mIsFillRequested.set(true); 1765 1766 // don't notify entered when Activity is already in background 1767 if (!isClientDisablingEnterExitEvent()) { 1768 if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) { 1769 flags |= FLAG_PASSWORD_INPUT_TYPE; 1770 } 1771 1772 // Update session when screen has credman field 1773 if (AutofillFeatureFlags.isFillAndSaveDialogDisabledForCredentialManager() 1774 && mHasCredentialField) { 1775 flags |= FLAG_SCREEN_HAS_CREDMAN_FIELD; 1776 if (sVerbose) { 1777 Log.v(TAG, "updating session with flag screen has credman view"); 1778 } 1779 } 1780 1781 flags |= getImeStateFlag(view); 1782 1783 if (!isActiveLocked()) { 1784 // Starts new session. 1785 startSessionLocked(id, bounds, value, flags); 1786 } else { 1787 // Update focus on existing session. 1788 if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) { 1789 if (sDebug) { 1790 Log.d(TAG, "notifyViewEntered(" + id + "): resetting " 1791 + "mForAugmentedAutofillOnly on manual request"); 1792 } 1793 mForAugmentedAutofillOnly = false; 1794 } 1795 1796 if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) { 1797 flags |= FLAG_RESET_FILL_DIALOG_STATE; 1798 } 1799 1800 updateSessionLocked(id, bounds, value, ACTION_VIEW_ENTERED, flags); 1801 } 1802 addEnteredIdLocked(id); 1803 } 1804 return null; 1805 } 1806 1807 @GuardedBy("mLock") addEnteredIdLocked(@onNull AutofillId id)1808 private void addEnteredIdLocked(@NonNull AutofillId id) { 1809 if (mEnteredIds == null) { 1810 mEnteredIds = new ArraySet<>(1); 1811 } 1812 id.resetSessionId(); 1813 mEnteredIds.add(id); 1814 } 1815 1816 /** 1817 * Called when a virtual view that supports autofill is exited. 1818 * 1819 * @param view the virtual view parent. 1820 * @param virtualId id identifying the virtual child inside the parent view. 1821 */ notifyViewExited(@onNull View view, int virtualId)1822 public void notifyViewExited(@NonNull View view, int virtualId) { 1823 if (sVerbose) Log.v(TAG, "notifyViewExited(" + view.getAutofillId() + ", " + virtualId); 1824 if (!hasAutofillFeature()) { 1825 return; 1826 } 1827 synchronized (mLock) { 1828 notifyViewExitedLocked(view, virtualId); 1829 } 1830 } 1831 1832 @GuardedBy("mLock") notifyViewExitedLocked(@onNull View view, int virtualId)1833 private void notifyViewExitedLocked(@NonNull View view, int virtualId) { 1834 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 1835 1836 if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) 1837 && isActiveLocked()) { 1838 // don't notify exited when Activity is already in background 1839 if (!isClientDisablingEnterExitEvent()) { 1840 final AutofillId id = getAutofillId(view, virtualId); 1841 1842 // Update focus on existing session. 1843 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 1844 } 1845 } 1846 } 1847 1848 /** 1849 * Called to indicate the value of an autofillable {@link View} changed. 1850 * 1851 * @param view view whose value changed. 1852 */ notifyValueChanged(View view)1853 public void notifyValueChanged(View view) { 1854 if (!hasAutofillFeature()) { 1855 return; 1856 } 1857 AutofillId id = null; 1858 boolean valueWasRead = false; 1859 AutofillValue value = null; 1860 1861 synchronized (mLock) { 1862 // If the session is gone some fields might still be highlighted, hence we have to 1863 // remove the isAutofilled property even if no sessions are active. 1864 if (mLastAutofilledData == null) { 1865 view.setAutofilled(false, false); 1866 } else { 1867 id = view.getAutofillId(); 1868 if (mLastAutofilledData.containsKey(id)) { 1869 value = view.getAutofillValue(); 1870 valueWasRead = true; 1871 final boolean hideHighlight = mLastAutofilledData.keySet().size() == 1; 1872 1873 if (Objects.equals(mLastAutofilledData.get(id), value)) { 1874 view.setAutofilled(true, hideHighlight); 1875 } else { 1876 view.setAutofilled(false, false); 1877 mLastAutofilledData.remove(id); 1878 } 1879 } else { 1880 view.setAutofilled(false, false); 1881 } 1882 } 1883 1884 if (!mEnabled || !isActiveLocked()) { 1885 if (!startAutofillIfNeededLocked(view)) { 1886 if (sVerbose) { 1887 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() 1888 + "): ignoring on state " + getStateAsStringLocked()); 1889 } 1890 } 1891 return; 1892 } 1893 1894 if (id == null) { 1895 id = view.getAutofillId(); 1896 } 1897 1898 if (!valueWasRead) { 1899 value = view.getAutofillValue(); 1900 } 1901 1902 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view)); 1903 } 1904 } 1905 1906 /** 1907 * Called to indicate the value of an autofillable virtual view has changed. 1908 * 1909 * @param view the virtual view parent. 1910 * @param virtualId id identifying the virtual child inside the parent view. 1911 * @param value new value of the child. 1912 */ notifyValueChanged(View view, int virtualId, AutofillValue value)1913 public void notifyValueChanged(View view, int virtualId, AutofillValue value) { 1914 if (!hasAutofillFeature()) { 1915 return; 1916 } 1917 synchronized (mLock) { 1918 if (!mEnabled || !isActiveLocked()) { 1919 if (sVerbose) { 1920 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId 1921 + "): ignoring on state " + getStateAsStringLocked()); 1922 } 1923 return; 1924 } 1925 1926 final AutofillId id = getAutofillId(view, virtualId); 1927 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view)); 1928 } 1929 } 1930 1931 /** 1932 * Called to indicate a {@link View} is clicked. 1933 * 1934 * @param view view that has been clicked. 1935 */ notifyViewClicked(@onNull View view)1936 public void notifyViewClicked(@NonNull View view) { 1937 notifyViewClicked(view.getAutofillId()); 1938 } 1939 1940 /** 1941 * Called to indicate a virtual view has been clicked. 1942 * 1943 * @param view the virtual view parent. 1944 * @param virtualId id identifying the virtual child inside the parent view. 1945 */ notifyViewClicked(@onNull View view, int virtualId)1946 public void notifyViewClicked(@NonNull View view, int virtualId) { 1947 notifyViewClicked(getAutofillId(view, virtualId)); 1948 } 1949 notifyViewClicked(AutofillId id)1950 private void notifyViewClicked(AutofillId id) { 1951 if (!hasAutofillFeature()) { 1952 return; 1953 } 1954 if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); 1955 1956 synchronized (mLock) { 1957 if (!mEnabled || !isActiveLocked()) { 1958 return; 1959 } 1960 if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { 1961 if (sDebug) Log.d(TAG, "triggering commit by click of " + id); 1962 commitLocked(/* commitReason= */ COMMIT_REASON_VIEW_CLICKED); 1963 mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED)); 1964 } 1965 } 1966 } 1967 1968 /** 1969 * Called by {@link android.app.Activity} to commit or cancel the session on finish. 1970 * 1971 * @hide 1972 */ onActivityFinishing()1973 public void onActivityFinishing() { 1974 if (!hasAutofillFeature()) { 1975 return; 1976 } 1977 synchronized (mLock) { 1978 if (mSaveOnFinish) { 1979 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling commitLocked()"); 1980 commitLocked(/* commitReason= */ COMMIT_REASON_ACTIVITY_FINISHED); 1981 } else { 1982 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling cancelLocked()"); 1983 cancelLocked(); 1984 } 1985 } 1986 } 1987 1988 /** 1989 * Called to indicate the current autofill context should be commited. 1990 * 1991 * <p>This method is typically called by {@link View Views} that manage virtual views; for 1992 * example, when the view is rendering an {@code HTML} page with a form and virtual views 1993 * that represent the HTML elements, it should call this method after the form is submitted and 1994 * another page is rendered. 1995 * 1996 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 1997 * methods such as {@link android.app.Activity#finish()}. 1998 */ commit()1999 public void commit() { 2000 if (!hasAutofillFeature()) { 2001 return; 2002 } 2003 if (sVerbose) Log.v(TAG, "commit() called by app"); 2004 synchronized (mLock) { 2005 commitLocked(/* commitReason= */ COMMIT_REASON_VIEW_COMMITTED); 2006 } 2007 } 2008 2009 @GuardedBy("mLock") commitLocked(@utofillCommitReason int commitReason)2010 private void commitLocked(@AutofillCommitReason int commitReason) { 2011 if (!mEnabled && !isActiveLocked()) { 2012 return; 2013 } 2014 finishSessionLocked(/* commitReason= */ commitReason); 2015 } 2016 2017 /** 2018 * Called to indicate the current autofill context should be cancelled. 2019 * 2020 * <p>This method is typically called by {@link View Views} that manage virtual views; for 2021 * example, when the view is rendering an {@code HTML} page with a form and virtual views 2022 * that represent the HTML elements, it should call this method if the user does not post the 2023 * form but moves to another form in this page. 2024 * 2025 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 2026 * methods such as {@link android.app.Activity#finish()}. 2027 */ cancel()2028 public void cancel() { 2029 if (sVerbose) Log.v(TAG, "cancel() called by app or augmented autofill service"); 2030 if (!hasAutofillFeature()) { 2031 return; 2032 } 2033 synchronized (mLock) { 2034 cancelLocked(); 2035 } 2036 } 2037 2038 @GuardedBy("mLock") cancelLocked()2039 private void cancelLocked() { 2040 if (!mEnabled && !isActiveLocked()) { 2041 return; 2042 } 2043 cancelSessionLocked(); 2044 } 2045 2046 /** @hide */ disableOwnedAutofillServices()2047 public void disableOwnedAutofillServices() { 2048 disableAutofillServices(); 2049 } 2050 2051 /** 2052 * If the app calling this API has enabled autofill services they 2053 * will be disabled. 2054 */ disableAutofillServices()2055 public void disableAutofillServices() { 2056 if (!hasAutofillFeature()) { 2057 return; 2058 } 2059 try { 2060 mService.disableOwnedAutofillServices(mContext.getUserId()); 2061 } catch (RemoteException e) { 2062 throw e.rethrowFromSystemServer(); 2063 } 2064 } 2065 2066 /** 2067 * Returns {@code true} if the calling application provides a {@link AutofillService} that is 2068 * enabled for the current user, or {@code false} otherwise. 2069 */ hasEnabledAutofillServices()2070 public boolean hasEnabledAutofillServices() { 2071 if (mService == null) return false; 2072 2073 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2074 try { 2075 mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), 2076 receiver); 2077 return receiver.getIntResult() == 1; 2078 } catch (RemoteException e) { 2079 throw e.rethrowFromSystemServer(); 2080 } catch (SyncResultReceiver.TimeoutException e) { 2081 throw new RuntimeException("Fail to get enabled autofill services status. " + e); 2082 } 2083 } 2084 2085 /** 2086 * Returns the component name of the {@link AutofillService} that is enabled for the current 2087 * user. 2088 */ 2089 @Nullable getAutofillServiceComponentName()2090 public ComponentName getAutofillServiceComponentName() { 2091 if (mService == null) return null; 2092 2093 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2094 try { 2095 mService.getAutofillServiceComponentName(receiver); 2096 return receiver.getParcelableResult(); 2097 } catch (RemoteException e) { 2098 throw e.rethrowFromSystemServer(); 2099 } catch (SyncResultReceiver.TimeoutException e) { 2100 throw new RuntimeException("Fail to get autofill services component name. " + e); 2101 } 2102 } 2103 2104 /** 2105 * Gets the id of the {@link UserData} used for 2106 * <a href="AutofillService.html#FieldClassification">field classification</a>. 2107 * 2108 * <p>This method is useful when the service must check the status of the {@link UserData} in 2109 * the device without fetching the whole object. 2110 * 2111 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2112 * and it's ignored if the caller currently doesn't have an enabled autofill service for 2113 * the user. 2114 * 2115 * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)} 2116 * or {@code null} if it was reset or if the caller currently does not have an enabled autofill 2117 * service for the user. 2118 */ getUserDataId()2119 @Nullable public String getUserDataId() { 2120 try { 2121 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2122 mService.getUserDataId(receiver); 2123 return receiver.getStringResult(); 2124 } catch (RemoteException e) { 2125 throw e.rethrowFromSystemServer(); 2126 } catch (SyncResultReceiver.TimeoutException e) { 2127 throw new RuntimeException("Fail to get user data id for field classification. " + e); 2128 } 2129 } 2130 2131 /** 2132 * Gets the user data used for 2133 * <a href="AutofillService.html#FieldClassification">field classification</a>. 2134 * 2135 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2136 * and it's ignored if the caller currently doesn't have an enabled autofill service for 2137 * the user. 2138 * 2139 * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was 2140 * reset or if the caller currently does not have an enabled autofill service for the user. 2141 */ getUserData()2142 @Nullable public UserData getUserData() { 2143 try { 2144 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2145 mService.getUserData(receiver); 2146 return receiver.getParcelableResult(); 2147 } catch (RemoteException e) { 2148 throw e.rethrowFromSystemServer(); 2149 } catch (SyncResultReceiver.TimeoutException e) { 2150 throw new RuntimeException("Fail to get user data for field classification. " + e); 2151 } 2152 } 2153 2154 /** 2155 * Sets the {@link UserData} used for 2156 * <a href="AutofillService.html#FieldClassification">field classification</a> 2157 * 2158 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2159 * and it's ignored if the caller currently doesn't have an enabled autofill service for 2160 * the user. 2161 */ setUserData(@ullable UserData userData)2162 public void setUserData(@Nullable UserData userData) { 2163 try { 2164 mService.setUserData(userData); 2165 } catch (RemoteException e) { 2166 throw e.rethrowFromSystemServer(); 2167 } 2168 } 2169 2170 /** 2171 * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is 2172 * enabled. 2173 * 2174 * <p>As field classification is an expensive operation, it could be disabled, either 2175 * temporarily (for example, because the service exceeded a rate-limit threshold) or 2176 * permanently (for example, because the device is a low-level device). 2177 * 2178 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2179 * and it's ignored if the caller currently doesn't have an enabled autofill service for 2180 * the user. 2181 */ isFieldClassificationEnabled()2182 public boolean isFieldClassificationEnabled() { 2183 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2184 try { 2185 mService.isFieldClassificationEnabled(receiver); 2186 return receiver.getIntResult() == 1; 2187 } catch (RemoteException e) { 2188 throw e.rethrowFromSystemServer(); 2189 } catch (SyncResultReceiver.TimeoutException e) { 2190 throw new RuntimeException("Fail to get field classification enabled status. " + e); 2191 } 2192 } 2193 2194 /** 2195 * Gets the name of the default algorithm used for 2196 * <a href="AutofillService.html#FieldClassification">field classification</a>. 2197 * 2198 * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not 2199 * set. 2200 * 2201 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2202 * and it's ignored if the caller currently doesn't have an enabled autofill service for 2203 * the user. 2204 */ 2205 @Nullable getDefaultFieldClassificationAlgorithm()2206 public String getDefaultFieldClassificationAlgorithm() { 2207 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2208 try { 2209 mService.getDefaultFieldClassificationAlgorithm(receiver); 2210 return receiver.getStringResult(); 2211 } catch (RemoteException e) { 2212 throw e.rethrowFromSystemServer(); 2213 } catch (SyncResultReceiver.TimeoutException e) { 2214 throw new RuntimeException("Fail to get default field classification algorithm. " + e); 2215 } 2216 } 2217 2218 /** 2219 * Gets the name of all algorithms currently available for 2220 * <a href="AutofillService.html#FieldClassification">field classification</a>. 2221 * 2222 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 2223 * and it returns an empty list if the caller currently doesn't have an enabled autofill service 2224 * for the user. 2225 */ 2226 @NonNull getAvailableFieldClassificationAlgorithms()2227 public List<String> getAvailableFieldClassificationAlgorithms() { 2228 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2229 try { 2230 mService.getAvailableFieldClassificationAlgorithms(receiver); 2231 final String[] algorithms = receiver.getStringArrayResult(); 2232 return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); 2233 } catch (RemoteException e) { 2234 throw e.rethrowFromSystemServer(); 2235 } catch (SyncResultReceiver.TimeoutException e) { 2236 throw new 2237 RuntimeException("Fail to get available field classification algorithms. " + e); 2238 } 2239 } 2240 2241 /** 2242 * Returns {@code true} if autofill is supported by the current device and 2243 * is supported for this user. 2244 * 2245 * <p>Autofill is typically supported, but it could be unsupported in cases like: 2246 * <ol> 2247 * <li>Low-end devices. 2248 * <li>Device policy rules that forbid its usage. 2249 * </ol> 2250 */ isAutofillSupported()2251 public boolean isAutofillSupported() { 2252 if (mService == null) return false; 2253 2254 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2255 try { 2256 mService.isServiceSupported(mContext.getUserId(), receiver); 2257 return receiver.getIntResult() == 1; 2258 } catch (RemoteException e) { 2259 throw e.rethrowFromSystemServer(); 2260 } catch (SyncResultReceiver.TimeoutException e) { 2261 throw new RuntimeException("Fail to get autofill supported status. " + e); 2262 } 2263 } 2264 2265 // Note: don't need to use locked suffix because mContext is final. getClient()2266 private AutofillClient getClient() { 2267 final AutofillClient client = mContext.getAutofillClient(); 2268 if (client == null && sVerbose) { 2269 Log.v(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " 2270 + mContext); 2271 } 2272 return client; 2273 } 2274 2275 /** 2276 * Check if autofill ui is showing, must be called on UI thread. 2277 * @hide 2278 */ isAutofillUiShowing()2279 public boolean isAutofillUiShowing() { 2280 final AutofillClient client = mContext.getAutofillClient(); 2281 return client != null && client.autofillClientIsFillUiShowing(); 2282 } 2283 2284 /** @hide */ onAuthenticationResult(int authenticationId, Intent data, View focusView)2285 public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { 2286 if (!hasAutofillFeature()) { 2287 return; 2288 } 2289 // TODO: the result code is being ignored, so this method is not reliably 2290 // handling the cases where it's not RESULT_OK: it works fine if the service does not 2291 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the 2292 // service set the extra and returned RESULT_CANCELED... 2293 2294 if (sDebug) { 2295 Log.d(TAG, "onAuthenticationResult(): id= " + authenticationId + ", data=" + data); 2296 } 2297 2298 synchronized (mLock) { 2299 if (!isActiveLocked()) { 2300 return; 2301 } 2302 // If authenticate activity closes itself during onCreate(), there is no onStop/onStart 2303 // of app activity. We enforce enter event to re-show fill ui in such case. 2304 // CTS example: 2305 // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt 2306 // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt 2307 if (!mOnInvisibleCalled && focusView != null 2308 && focusView.canNotifyAutofillEnterExitEvent()) { 2309 notifyViewExitedLocked(focusView); 2310 notifyViewEnteredLocked(focusView, focusView.getAutofillId(), 2311 /* bounds= */ null, focusView.getAutofillValue(), /* flags= */ 0); 2312 } 2313 if (data == null) { 2314 // data is set to null when result is not RESULT_OK 2315 return; 2316 } 2317 2318 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); 2319 final Bundle responseData = new Bundle(); 2320 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); 2321 final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); 2322 if (newClientState != null) { 2323 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); 2324 } 2325 if (data.hasExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { 2326 responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET, 2327 data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET, 2328 false)); 2329 } 2330 try { 2331 mService.setAuthenticationResult(responseData, mSessionId, authenticationId, 2332 mContext.getUserId()); 2333 } catch (RemoteException e) { 2334 Log.e(TAG, "Error delivering authentication result", e); 2335 } 2336 } 2337 } 2338 2339 /** 2340 * Gets the next unique autofill ID for the activity context. 2341 * 2342 * <p>Typically used to manage views whose content is recycled - see 2343 * {@link View#setAutofillId(AutofillId)} for more info. 2344 * 2345 * @return An ID that is unique in the activity, or {@code null} if autofill is not supported in 2346 * the {@link Context} associated with this {@link AutofillManager}. 2347 */ 2348 @Nullable getNextAutofillId()2349 public AutofillId getNextAutofillId() { 2350 final AutofillClient client = getClient(); 2351 if (client == null) return null; 2352 2353 final AutofillId id = client.autofillClientGetNextAutofillId(); 2354 2355 if (id == null && sDebug) { 2356 Log.d(TAG, "getNextAutofillId(): client " + client + " returned null"); 2357 } 2358 2359 return id; 2360 } 2361 getAutofillId(View parent, int virtualId)2362 private static AutofillId getAutofillId(View parent, int virtualId) { 2363 return new AutofillId(parent.getAutofillViewId(), virtualId); 2364 } 2365 2366 @GuardedBy("mLock") startSessionLocked(@onNull AutofillId id, @NonNull Rect bounds, @NonNull AutofillValue value, int flags)2367 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, 2368 @NonNull AutofillValue value, int flags) { 2369 if (mEnteredForAugmentedAutofillIds != null 2370 && mEnteredForAugmentedAutofillIds.contains(id) 2371 || mEnabledForAugmentedAutofillOnly) { 2372 if (sVerbose) Log.v(TAG, "Starting session for augmented autofill on " + id); 2373 flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; 2374 } 2375 if (sVerbose) { 2376 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value 2377 + ", flags=" + flags + ", state=" + getStateAsStringLocked() 2378 + ", compatMode=" + isCompatibilityModeEnabledLocked() 2379 + ", augmentedOnly=" + mForAugmentedAutofillOnly 2380 + ", enabledAugmentedOnly=" + mEnabledForAugmentedAutofillOnly 2381 + ", enteredIds=" + mEnteredIds); 2382 } 2383 // We need to reset the augmented-only state when a manual request is made, as it's possible 2384 // that the service returned null for the first request and now the user is manually 2385 // requesting autofill to trigger a custom UI provided by the service. 2386 if (mForAugmentedAutofillOnly && !mEnabledForAugmentedAutofillOnly 2387 && (flags & FLAG_MANUAL_REQUEST) != 0) { 2388 if (sVerbose) { 2389 Log.v(TAG, "resetting mForAugmentedAutofillOnly on manual autofill request"); 2390 } 2391 mForAugmentedAutofillOnly = false; 2392 } 2393 if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { 2394 if (sVerbose) { 2395 Log.v(TAG, "not automatically starting session for " + id 2396 + " on state " + getStateAsStringLocked() + " and flags " + flags); 2397 } 2398 return; 2399 } 2400 try { 2401 final AutofillClient client = getClient(); 2402 if (client == null) return; // NOTE: getClient() already logged it.. 2403 2404 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2405 final ComponentName clientActivity = client.autofillClientGetComponentName(); 2406 2407 if (!mEnabledForAugmentedAutofillOnly && mOptions != null 2408 && mOptions.isAutofillDisabledLocked(clientActivity)) { 2409 if (mOptions.isAugmentedAutofillEnabled(mContext)) { 2410 if (sDebug) { 2411 Log.d(TAG, "startSession(" + clientActivity + "): disabled by service but " 2412 + "allowlisted for augmented autofill"); 2413 flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; 2414 } 2415 } else { 2416 if (sDebug) { 2417 Log.d(TAG, "startSession(" + clientActivity + "): ignored because " 2418 + "disabled by service and not allowlisted for augmented autofill"); 2419 } 2420 setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, null); 2421 client.autofillClientResetableStateAvailable(); 2422 return; 2423 } 2424 } 2425 2426 mService.startSession(client.autofillClientGetActivityToken(), 2427 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 2428 mCallback != null, flags, clientActivity, 2429 isCompatibilityModeEnabledLocked(), receiver); 2430 mSessionId = receiver.getIntResult(); 2431 if (mSessionId != NO_SESSION) { 2432 mState = STATE_ACTIVE; 2433 } 2434 final int extraFlags = receiver.getOptionalExtraIntResult(0); 2435 if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) { 2436 if (sDebug) Log.d(TAG, "startSession(" + clientActivity + "): for augmented only"); 2437 mForAugmentedAutofillOnly = true; 2438 } 2439 client.autofillClientResetableStateAvailable(); 2440 } catch (RemoteException e) { 2441 throw e.rethrowFromSystemServer(); 2442 } catch (SyncResultReceiver.TimeoutException e) { 2443 // no-op, just log the error message. 2444 Log.w(TAG, "Exception getting result from SyncResultReceiver: " + e); 2445 } 2446 } 2447 2448 @GuardedBy("mLock") finishSessionLocked(@utofillCommitReason int commitReason)2449 private void finishSessionLocked(@AutofillCommitReason int commitReason) { 2450 if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); 2451 2452 if (!isActiveLocked()) return; 2453 2454 try { 2455 mService.finishSession(mSessionId, mContext.getUserId(), commitReason); 2456 } catch (RemoteException e) { 2457 throw e.rethrowFromSystemServer(); 2458 } 2459 2460 resetSessionLocked(/* resetEnteredIds= */ true); 2461 } 2462 2463 @GuardedBy("mLock") cancelSessionLocked()2464 private void cancelSessionLocked() { 2465 if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); 2466 2467 if (!isActiveLocked()) return; 2468 2469 try { 2470 mService.cancelSession(mSessionId, mContext.getUserId()); 2471 } catch (RemoteException e) { 2472 throw e.rethrowFromSystemServer(); 2473 } 2474 2475 resetSessionLocked(/* resetEnteredIds= */ true); 2476 } 2477 2478 @GuardedBy("mLock") resetSessionLocked(boolean resetEnteredIds)2479 private void resetSessionLocked(boolean resetEnteredIds) { 2480 mSessionId = NO_SESSION; 2481 mState = STATE_UNKNOWN; 2482 mTrackedViews = null; 2483 mFillableIds = null; 2484 mSaveTriggerId = null; 2485 mIdShownFillUi = null; 2486 mIsFillRequested.set(false); 2487 mShowAutofillDialogCalled = false; 2488 mFillDialogTriggerIds = null; 2489 mHasCredentialField = false; 2490 mAllTrackedViews.clear(); 2491 if (resetEnteredIds) { 2492 mEnteredIds = null; 2493 } 2494 } 2495 2496 @GuardedBy("mLock") updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, int flags)2497 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, 2498 int flags) { 2499 if (sVerbose) { 2500 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds 2501 + ", value=" + value + ", action=" + action + ", flags=" + flags); 2502 } 2503 try { 2504 mService.updateSession(mSessionId, id, bounds, value, action, flags, 2505 mContext.getUserId()); 2506 } catch (RemoteException e) { 2507 throw e.rethrowFromSystemServer(); 2508 } 2509 } 2510 2511 /** 2512 * Tries to add AutofillManagerClient to service if it does not been added. Returns {@code true} 2513 * if the AutofillManagerClient is added successfully or is already added. Otherwise, 2514 * returns {@code false}. 2515 */ 2516 @GuardedBy("mLock") tryAddServiceClientIfNeededLocked()2517 private boolean tryAddServiceClientIfNeededLocked() { 2518 final AutofillClient client = getClient(); 2519 if (client == null) { 2520 return false; 2521 } 2522 if (mService == null) { 2523 Log.w(TAG, "Autofill service is null!"); 2524 return false; 2525 } 2526 if (mServiceClient == null) { 2527 mServiceClient = new AutofillManagerClient(this); 2528 try { 2529 final int userId = mContext.getUserId(); 2530 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2531 mService.addClient(mServiceClient, client.autofillClientGetComponentName(), 2532 userId, receiver); 2533 int flags = 0; 2534 try { 2535 flags = receiver.getIntResult(); 2536 } catch (SyncResultReceiver.TimeoutException e) { 2537 Log.w(TAG, "Failed to initialize autofill: " + e); 2538 // Reset the states initialized above. 2539 mService.removeClient(mServiceClient, userId); 2540 mServiceClient = null; 2541 return false; 2542 } 2543 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; 2544 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; 2545 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; 2546 mEnabledForAugmentedAutofillOnly = (flags 2547 & FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY) != 0; 2548 if (sVerbose) { 2549 Log.v(TAG, "receiver results: flags=" + flags + " enabled=" + mEnabled 2550 + ", enabledForAugmentedOnly: " + mEnabledForAugmentedAutofillOnly); 2551 } 2552 final IAutoFillManager service = mService; 2553 final IAutoFillManagerClient serviceClient = mServiceClient; 2554 mServiceClientCleaner = Cleaner.create(this, () -> { 2555 // TODO(b/123100811): call service to also remove reference to 2556 // mAugmentedAutofillServiceClient 2557 try { 2558 service.removeClient(serviceClient, userId); 2559 } catch (RemoteException e) { 2560 } 2561 }); 2562 } catch (RemoteException e) { 2563 throw e.rethrowFromSystemServer(); 2564 } 2565 } 2566 return true; 2567 } 2568 2569 @GuardedBy("mLock") startAutofillIfNeededLocked(View view)2570 private boolean startAutofillIfNeededLocked(View view) { 2571 if (mState == STATE_UNKNOWN 2572 && mSessionId == NO_SESSION 2573 && view instanceof EditText 2574 && !TextUtils.isEmpty(((EditText) view).getText()) 2575 && !view.isFocused() 2576 && view.isImportantForAutofill() 2577 && view.isLaidOut() 2578 && view.isVisibleToUser()) { 2579 2580 final boolean clientAdded = tryAddServiceClientIfNeededLocked(); 2581 2582 if (sVerbose) { 2583 Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled + " mServiceClient=" 2584 + mServiceClient); 2585 } 2586 if (clientAdded && mEnabled && !isClientDisablingEnterExitEvent()) { 2587 final AutofillId id = view.getAutofillId(); 2588 final AutofillValue value = view.getAutofillValue(); 2589 // Starts new session. 2590 startSessionLocked(id, /* bounds= */ null, /* value= */ null, /* flags= */ 0); 2591 // Updates value. 2592 updateSessionLocked(id, /* bounds= */ null, value, ACTION_VALUE_CHANGED, 2593 /* flags= */ 0); 2594 addEnteredIdLocked(id); 2595 return true; 2596 } 2597 } 2598 return false; 2599 } 2600 2601 /** 2602 * Registers a {@link AutofillCallback} to receive autofill events. 2603 * 2604 * @param callback callback to receive events. 2605 */ registerCallback(@ullable AutofillCallback callback)2606 public void registerCallback(@Nullable AutofillCallback callback) { 2607 if (!hasAutofillFeature()) { 2608 return; 2609 } 2610 synchronized (mLock) { 2611 if (callback == null) return; 2612 2613 final boolean hadCallback = mCallback != null; 2614 mCallback = callback; 2615 2616 if (!hadCallback) { 2617 try { 2618 mService.setHasCallback(mSessionId, mContext.getUserId(), true); 2619 } catch (RemoteException e) { 2620 throw e.rethrowFromSystemServer(); 2621 } 2622 } 2623 } 2624 } 2625 2626 /** 2627 * Unregisters a {@link AutofillCallback} to receive autofill events. 2628 * 2629 * @param callback callback to stop receiving events. 2630 */ unregisterCallback(@ullable AutofillCallback callback)2631 public void unregisterCallback(@Nullable AutofillCallback callback) { 2632 if (!hasAutofillFeature()) { 2633 return; 2634 } 2635 synchronized (mLock) { 2636 if (callback == null || mCallback == null || callback != mCallback) return; 2637 2638 mCallback = null; 2639 2640 try { 2641 mService.setHasCallback(mSessionId, mContext.getUserId(), false); 2642 } catch (RemoteException e) { 2643 throw e.rethrowFromSystemServer(); 2644 } 2645 } 2646 } 2647 2648 /** 2649 * Explicitly limits augmented autofill to the given packages and activities. 2650 * 2651 * <p>To reset the allowlist, call it passing {@code null} to both arguments. 2652 * 2653 * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like 2654 * apps that uses addresses. For example, if the service wants to support augmented autofill on 2655 * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2} 2656 * of {@code AddressApp2}, it would call: 2657 * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"), 2658 * Arrays.asList(new ComponentName("AddressApp2", "act1"), 2659 * new ComponentName("AddressApp2", "act2")));} 2660 * 2661 * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill 2662 * service, and it's ignored if the caller isn't it. 2663 * 2664 * @hide 2665 */ 2666 @SystemApi setAugmentedAutofillWhitelist(@ullable Set<String> packages, @Nullable Set<ComponentName> activities)2667 public void setAugmentedAutofillWhitelist(@Nullable Set<String> packages, 2668 @Nullable Set<ComponentName> activities) { 2669 if (!hasAutofillFeature()) { 2670 return; 2671 } 2672 2673 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 2674 int resultCode; 2675 try { 2676 mService.setAugmentedAutofillWhitelist(toList(packages), toList(activities), 2677 resultReceiver); 2678 resultCode = resultReceiver.getIntResult(); 2679 } catch (RemoteException e) { 2680 throw e.rethrowFromSystemServer(); 2681 } catch (SyncResultReceiver.TimeoutException e) { 2682 Log.e(TAG, "Fail to get the result of set AugmentedAutofill whitelist. " + e); 2683 return; 2684 } 2685 switch (resultCode) { 2686 case RESULT_OK: 2687 return; 2688 case RESULT_CODE_NOT_SERVICE: 2689 throw new SecurityException("caller is not user's Augmented Autofill Service"); 2690 default: 2691 Log.wtf(TAG, "setAugmentedAutofillWhitelist(): received invalid result: " 2692 + resultCode); 2693 } 2694 } 2695 2696 /** 2697 * Notifies that a non-autofillable view was entered because the activity is allowlisted for 2698 * augmented autofill. 2699 * 2700 * <p>This method is necessary to set the right flag on start, so the server-side session 2701 * doesn't trigger the standard autofill workflow, but the augmented's instead. 2702 * 2703 * @hide 2704 */ notifyViewEnteredForAugmentedAutofill(@onNull View view)2705 public void notifyViewEnteredForAugmentedAutofill(@NonNull View view) { 2706 final AutofillId id = view.getAutofillId(); 2707 synchronized (mLock) { 2708 if (mEnteredForAugmentedAutofillIds == null) { 2709 mEnteredForAugmentedAutofillIds = new ArraySet<>(1); 2710 } 2711 mEnteredForAugmentedAutofillIds.add(id); 2712 } 2713 } 2714 requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)2715 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 2716 Rect anchorBounds, IAutofillWindowPresenter presenter) { 2717 final View anchor = findView(id); 2718 if (anchor == null) { 2719 return; 2720 } 2721 2722 AutofillCallback callback = null; 2723 synchronized (mLock) { 2724 if (mSessionId == sessionId) { 2725 AutofillClient client = getClient(); 2726 2727 if (client != null) { 2728 if (client.autofillClientRequestShowFillUi(anchor, width, height, 2729 anchorBounds, presenter)) { 2730 callback = mCallback; 2731 mIdShownFillUi = id; 2732 } 2733 } 2734 } 2735 } 2736 2737 if (callback != null) { 2738 if (id.isVirtualInt()) { 2739 callback.onAutofillEvent(anchor, id.getVirtualChildIntId(), 2740 AutofillCallback.EVENT_INPUT_SHOWN); 2741 } else { 2742 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); 2743 } 2744 } 2745 } 2746 authenticate(int sessionId, int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline)2747 private void authenticate(int sessionId, int authenticationId, IntentSender intent, 2748 Intent fillInIntent, boolean authenticateInline) { 2749 synchronized (mLock) { 2750 if (sessionId == mSessionId) { 2751 final AutofillClient client = getClient(); 2752 if (client != null) { 2753 // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() 2754 // before onAuthenticationResult() 2755 mOnInvisibleCalled = false; 2756 client.autofillClientAuthenticate(authenticationId, intent, fillInIntent, 2757 authenticateInline); 2758 } 2759 } 2760 } 2761 } 2762 dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent)2763 private void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent) { 2764 final View anchor = findView(id); 2765 if (anchor == null) { 2766 return; 2767 } 2768 2769 synchronized (mLock) { 2770 if (mSessionId == sessionId) { 2771 AutofillClient client = getClient(); 2772 2773 if (client != null) { 2774 client.autofillClientDispatchUnhandledKey(anchor, keyEvent); 2775 } 2776 } 2777 } 2778 } 2779 2780 /** @hide */ 2781 public static final int SET_STATE_FLAG_ENABLED = 0x01; 2782 /** @hide */ 2783 public static final int SET_STATE_FLAG_RESET_SESSION = 0x02; 2784 /** @hide */ 2785 public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04; 2786 /** @hide */ 2787 public static final int SET_STATE_FLAG_DEBUG = 0x08; 2788 /** @hide */ 2789 public static final int SET_STATE_FLAG_VERBOSE = 0x10; 2790 /** @hide */ 2791 public static final int SET_STATE_FLAG_FOR_AUTOFILL_ONLY = 0x20; 2792 setState(int flags)2793 private void setState(int flags) { 2794 if (sVerbose) { 2795 Log.v(TAG, "setState(" + flags + ": " + DebugUtils.flagsToString(AutofillManager.class, 2796 "SET_STATE_FLAG_", flags) + ")"); 2797 } 2798 synchronized (mLock) { 2799 if ((flags & SET_STATE_FLAG_FOR_AUTOFILL_ONLY) != 0) { 2800 mForAugmentedAutofillOnly = true; 2801 // NOTE: returning right away as this is the only flag set, at least currently... 2802 return; 2803 } 2804 mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; 2805 if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { 2806 // Reset the session state 2807 resetSessionLocked(/* resetEnteredIds= */ true); 2808 } 2809 if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { 2810 // Reset connection to system 2811 mServiceClient = null; 2812 mAugmentedAutofillServiceClient = null; 2813 if (mServiceClientCleaner != null) { 2814 mServiceClientCleaner.clean(); 2815 mServiceClientCleaner = null; 2816 } 2817 notifyReenableAutofill(); 2818 } 2819 } 2820 sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; 2821 sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; 2822 } 2823 2824 /** 2825 * Sets a view as autofilled if the current value is the {code targetValue}. 2826 * 2827 * @param view The view that is to be autofilled 2828 * @param targetValue The value we want to fill into view 2829 */ setAutofilledIfValuesIs(@onNull View view, @Nullable AutofillValue targetValue, boolean hideHighlight)2830 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue, 2831 boolean hideHighlight) { 2832 AutofillValue currentValue = view.getAutofillValue(); 2833 if (Objects.equals(currentValue, targetValue)) { 2834 synchronized (mLock) { 2835 if (mLastAutofilledData == null) { 2836 mLastAutofilledData = new ParcelableMap(1); 2837 } 2838 mLastAutofilledData.put(view.getAutofillId(), targetValue); 2839 } 2840 view.setAutofilled(true, hideHighlight); 2841 } 2842 } 2843 autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, boolean hideHighlight)2844 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, 2845 boolean hideHighlight) { 2846 synchronized (mLock) { 2847 if (sessionId != mSessionId) { 2848 return; 2849 } 2850 2851 final AutofillClient client = getClient(); 2852 if (client == null) { 2853 return; 2854 } 2855 2856 final int itemCount = ids.size(); 2857 int numApplied = 0; 2858 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; 2859 final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( 2860 Helper.toArray(ids)); 2861 2862 ArrayList<AutofillId> failedIds = null; 2863 2864 for (int i = 0; i < itemCount; i++) { 2865 final AutofillId id = ids.get(i); 2866 final AutofillValue value = values.get(i); 2867 final View view = views[i]; 2868 if (view == null) { 2869 // Most likely view has been removed after the initial request was sent to the 2870 // the service; this is fine, but we need to update the view status in the 2871 // server side so it can be triggered again. 2872 Log.d(TAG, "autofill(): no View with id " + id); 2873 if (failedIds == null) { 2874 failedIds = new ArrayList<>(); 2875 } 2876 failedIds.add(id); 2877 continue; 2878 } 2879 if (id.isVirtualInt()) { 2880 if (virtualValues == null) { 2881 // Most likely there will be just one view with virtual children. 2882 virtualValues = new ArrayMap<>(1); 2883 } 2884 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); 2885 if (valuesByParent == null) { 2886 // We don't know the size yet, but usually it will be just a few fields... 2887 valuesByParent = new SparseArray<>(5); 2888 virtualValues.put(view, valuesByParent); 2889 } 2890 valuesByParent.put(id.getVirtualChildIntId(), value); 2891 } else { 2892 // Mark the view as to be autofilled with 'value' 2893 if (mLastAutofilledData == null) { 2894 mLastAutofilledData = new ParcelableMap(itemCount - i); 2895 } 2896 mLastAutofilledData.put(id, value); 2897 2898 view.autofill(value); 2899 2900 // Set as autofilled if the values match now, e.g. when the value was updated 2901 // synchronously. 2902 // If autofill happens async, the view is set to autofilled in 2903 // notifyValueChanged. 2904 setAutofilledIfValuesIs(view, value, hideHighlight); 2905 2906 numApplied++; 2907 } 2908 } 2909 2910 if (failedIds != null) { 2911 if (sVerbose) { 2912 Log.v(TAG, "autofill(): total failed views: " + failedIds); 2913 } 2914 try { 2915 mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId()); 2916 } catch (RemoteException e) { 2917 // In theory, we could ignore this error since it's not a big deal, but 2918 // in reality, we rather crash the app anyways, as the failure could be 2919 // a consequence of something going wrong on the server side... 2920 throw e.rethrowFromSystemServer(); 2921 } 2922 } 2923 2924 if (virtualValues != null) { 2925 for (int i = 0; i < virtualValues.size(); i++) { 2926 final View parent = virtualValues.keyAt(i); 2927 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); 2928 parent.autofill(childrenValues); 2929 numApplied += childrenValues.size(); 2930 // TODO: we should provide a callback so the parent can call failures; something 2931 // like notifyAutofillFailed(View view, int[] childrenIds); 2932 } 2933 } 2934 2935 mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED) 2936 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) 2937 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied)); 2938 } 2939 } 2940 autofillContent(int sessionId, AutofillId id, ClipData clip)2941 private void autofillContent(int sessionId, AutofillId id, ClipData clip) { 2942 synchronized (mLock) { 2943 if (sessionId != mSessionId) { 2944 return; 2945 } 2946 final AutofillClient client = getClient(); 2947 if (client == null) { 2948 return; 2949 } 2950 final View view = client.autofillClientFindViewByAutofillIdTraversal(id); 2951 if (view == null) { 2952 // Most likely view has been removed after the initial request was sent to the 2953 // the service; this is fine, but we need to update the view status in the 2954 // server side so it can be triggered again. 2955 Log.d(TAG, "autofillContent(): no view with id " + id); 2956 reportAutofillContentFailure(id); 2957 return; 2958 } 2959 ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 2960 ContentInfo result = view.performReceiveContent(payload); 2961 if (result != null) { 2962 Log.w(TAG, "autofillContent(): receiver could not insert content: id=" + id 2963 + ", view=" + view + ", clip=" + clip); 2964 reportAutofillContentFailure(id); 2965 return; 2966 } 2967 mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED) 2968 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, 1) 2969 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, 1)); 2970 } 2971 } 2972 reportAutofillContentFailure(AutofillId id)2973 private void reportAutofillContentFailure(AutofillId id) { 2974 try { 2975 mService.setAutofillFailure(mSessionId, Collections.singletonList(id), 2976 mContext.getUserId()); 2977 } catch (RemoteException e) { 2978 throw e.rethrowFromSystemServer(); 2979 } 2980 } 2981 newLog(int category)2982 private LogMaker newLog(int category) { 2983 final LogMaker log = new LogMaker(category) 2984 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId); 2985 2986 if (isCompatibilityModeEnabledLocked()) { 2987 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); 2988 } 2989 final AutofillClient client = getClient(); 2990 if (client == null) { 2991 // Client should never be null here, but it doesn't hurt to check... 2992 log.setPackageName(mContext.getPackageName()); 2993 } else { 2994 // Remove activity name from logging 2995 final ComponentName sanitizedComponentName = 2996 new ComponentName(client.autofillClientGetComponentName().getPackageName(), ""); 2997 log.setComponentName(sanitizedComponentName); 2998 } 2999 return log; 3000 } 3001 3002 /** 3003 * Set the tracked views. 3004 * 3005 * @param trackedIds The views to be tracked. 3006 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. 3007 * @param saveOnFinish Finish the session once the activity is finished. 3008 * @param fillableIds Views that might anchor FillUI. 3009 * @param saveTriggerId View that when clicked triggers commit(). 3010 */ setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, boolean saveOnAllViewsInvisible, boolean saveOnFinish, @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId)3011 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, 3012 boolean saveOnAllViewsInvisible, boolean saveOnFinish, 3013 @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { 3014 if (saveTriggerId != null) { 3015 saveTriggerId.resetSessionId(); 3016 } 3017 synchronized (mLock) { 3018 if (sVerbose) { 3019 Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId 3020 + ", trackedIds=" + Arrays.toString(trackedIds) 3021 + ", saveOnAllViewsInvisible=" + saveOnAllViewsInvisible 3022 + ", saveOnFinish=" + saveOnFinish 3023 + ", fillableIds=" + Arrays.toString(fillableIds) 3024 + ", saveTrigerId=" + saveTriggerId 3025 + ", mFillableIds=" + mFillableIds 3026 + ", mEnabled=" + mEnabled 3027 + ", mSessionId=" + mSessionId); 3028 } 3029 3030 if (mEnabled && mSessionId == sessionId) { 3031 mSaveOnFinish = saveOnFinish; 3032 if (fillableIds != null) { 3033 if (mFillableIds == null) { 3034 mFillableIds = new ArraySet<>(fillableIds.length); 3035 } 3036 for (AutofillId id : fillableIds) { 3037 if (id != null) { 3038 id.resetSessionId(); 3039 mFillableIds.add(id); 3040 } 3041 } 3042 } 3043 3044 if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { 3045 // Turn off trigger on previous view id. 3046 setNotifyOnClickLocked(mSaveTriggerId, false); 3047 } 3048 3049 if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { 3050 // Turn on trigger on new view id. 3051 mSaveTriggerId = saveTriggerId; 3052 setNotifyOnClickLocked(mSaveTriggerId, true); 3053 } 3054 3055 if (!saveOnAllViewsInvisible) { 3056 trackedIds = null; 3057 } 3058 3059 final ArraySet<AutofillId> allFillableIds = new ArraySet<>(); 3060 if (mFillableIds != null) { 3061 allFillableIds.addAll(mFillableIds); 3062 } 3063 if (trackedIds != null) { 3064 for (AutofillId id : trackedIds) { 3065 if (id != null) { 3066 id.resetSessionId(); 3067 allFillableIds.add(id); 3068 } 3069 } 3070 } 3071 3072 if (!allFillableIds.isEmpty()) { 3073 mTrackedViews = new TrackedViews(trackedIds, Helper.toArray(allFillableIds)); 3074 } else { 3075 mTrackedViews = null; 3076 } 3077 } 3078 } 3079 } 3080 setNotifyOnClickLocked(@onNull AutofillId id, boolean notify)3081 private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { 3082 final View view = findView(id); 3083 if (view == null) { 3084 Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); 3085 return; 3086 } 3087 view.setNotifyAutofillManagerOnClick(notify); 3088 } 3089 setSaveUiState(int sessionId, boolean shown)3090 private void setSaveUiState(int sessionId, boolean shown) { 3091 if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); 3092 synchronized (mLock) { 3093 if (mSessionId != NO_SESSION) { 3094 // Race condition: app triggered a new session after the previous session was 3095 // finished but before server called setSaveUiState() - need to cancel the new 3096 // session to avoid further inconsistent behavior. 3097 Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown 3098 + ") called on existing session " + mSessionId + "; cancelling it"); 3099 cancelSessionLocked(); 3100 } 3101 if (shown) { 3102 mSessionId = sessionId; 3103 mState = STATE_SHOWING_SAVE_UI; 3104 } else { 3105 mSessionId = NO_SESSION; 3106 mState = STATE_UNKNOWN; 3107 } 3108 } 3109 } 3110 3111 /** 3112 * Marks the state of the session as finished. 3113 * 3114 * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} 3115 * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), 3116 * {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar 3117 * changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished 3118 * when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE} 3119 * (because the autofill service disabled further autofill requests for the activity). 3120 * @param autofillableIds list of ids that could trigger autofill, use to not handle a new 3121 * session when they're entered. 3122 */ setSessionFinished(int newState, @Nullable List<AutofillId> autofillableIds)3123 private void setSessionFinished(int newState, @Nullable List<AutofillId> autofillableIds) { 3124 if (autofillableIds != null) { 3125 for (int i = 0; i < autofillableIds.size(); i++) { 3126 autofillableIds.get(i).resetSessionId(); 3127 } 3128 } 3129 synchronized (mLock) { 3130 if (sVerbose) { 3131 Log.v(TAG, "setSessionFinished(): from " + getStateAsStringLocked() + " to " 3132 + getStateAsString(newState) + "; autofillableIds=" + autofillableIds); 3133 } 3134 if (autofillableIds != null) { 3135 mEnteredIds = new ArraySet<>(autofillableIds); 3136 } 3137 if (newState == STATE_UNKNOWN_COMPAT_MODE || newState == STATE_UNKNOWN_FAILED) { 3138 resetSessionLocked(/* resetEnteredIds= */ true); 3139 mState = STATE_UNKNOWN; 3140 } else { 3141 resetSessionLocked(/* resetEnteredIds= */ false); 3142 mState = newState; 3143 } 3144 } 3145 } 3146 3147 /** 3148 * Gets a {@link AugmentedAutofillManagerClient} for this {@link AutofillManagerClient}. 3149 * 3150 * <p>These are 2 distinct objects because we need to restrict what the Augmented Autofill 3151 * service can do (which is defined by {@code IAugmentedAutofillManagerClient.aidl}). 3152 */ getAugmentedAutofillClient(@onNull IResultReceiver result)3153 private void getAugmentedAutofillClient(@NonNull IResultReceiver result) { 3154 synchronized (mLock) { 3155 if (mAugmentedAutofillServiceClient == null) { 3156 mAugmentedAutofillServiceClient = new AugmentedAutofillManagerClient(this); 3157 } 3158 final Bundle resultData = new Bundle(); 3159 resultData.putBinder(EXTRA_AUGMENTED_AUTOFILL_CLIENT, 3160 mAugmentedAutofillServiceClient.asBinder()); 3161 3162 try { 3163 result.send(0, resultData); 3164 } catch (RemoteException e) { 3165 Log.w(TAG, "Could not send AugmentedAutofillClient back: " + e); 3166 } 3167 } 3168 } 3169 requestShowSoftInput(@onNull AutofillId id)3170 private void requestShowSoftInput(@NonNull AutofillId id) { 3171 if (sVerbose) Log.v(TAG, "requestShowSoftInput(" + id + ")"); 3172 final AutofillClient client = getClient(); 3173 if (client == null) { 3174 return; 3175 } 3176 final View view = client.autofillClientFindViewByAutofillIdTraversal(id); 3177 if (view == null) { 3178 if (sVerbose) Log.v(TAG, "View is not found"); 3179 return; 3180 } 3181 final Handler handler = view.getHandler(); 3182 if (handler == null) { 3183 if (sVerbose) Log.v(TAG, "Ignoring requestShowSoftInput due to no handler in view"); 3184 return; 3185 } 3186 if (handler.getLooper() != Looper.myLooper()) { 3187 // The view is running on a different thread than our own, so we need to reschedule 3188 // our work for over there. 3189 if (sVerbose) Log.v(TAG, "Scheduling showSoftInput() on the view UI thread"); 3190 handler.post(() -> requestShowSoftInputInViewThread(view)); 3191 } else { 3192 requestShowSoftInputInViewThread(view); 3193 } 3194 } 3195 3196 // This method must be called from within the View thread. requestShowSoftInputInViewThread(@onNull View view)3197 private static void requestShowSoftInputInViewThread(@NonNull View view) { 3198 if (!view.isFocused()) { 3199 Log.w(TAG, "Ignoring requestShowSoftInput() due to non-focused view"); 3200 return; 3201 } 3202 final InputMethodManager inputMethodManager = view.getContext().getSystemService( 3203 InputMethodManager.class); 3204 boolean ret = inputMethodManager.showSoftInput(view, /*flags=*/ 0); 3205 if (sVerbose) Log.v(TAG, " InputMethodManager.showSoftInput returns " + ret); 3206 } 3207 3208 /** @hide */ requestHideFillUi()3209 public void requestHideFillUi() { 3210 requestHideFillUi(mIdShownFillUi, true); 3211 } 3212 requestHideFillUi(AutofillId id, boolean force)3213 private void requestHideFillUi(AutofillId id, boolean force) { 3214 final View anchor = id == null ? null : findView(id); 3215 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor); 3216 if (anchor == null) { 3217 if (force) { 3218 // When user taps outside autofill window, force to close fill ui even id does 3219 // not match. 3220 AutofillClient client = getClient(); 3221 if (client != null) { 3222 client.autofillClientRequestHideFillUi(); 3223 } 3224 } 3225 return; 3226 } 3227 requestHideFillUi(id, anchor); 3228 } 3229 requestHideFillUi(AutofillId id, View anchor)3230 private void requestHideFillUi(AutofillId id, View anchor) { 3231 3232 AutofillCallback callback = null; 3233 synchronized (mLock) { 3234 // We do not check the session id for two reasons: 3235 // 1. If local and remote session id are off sync the UI would be stuck shown 3236 // 2. There is a race between the user state being destroyed due the fill 3237 // service being uninstalled and the UI being dismissed. 3238 AutofillClient client = getClient(); 3239 if (client != null) { 3240 if (client.autofillClientRequestHideFillUi()) { 3241 mIdShownFillUi = null; 3242 callback = mCallback; 3243 } 3244 } 3245 } 3246 3247 if (callback != null) { 3248 if (id.isVirtualInt()) { 3249 callback.onAutofillEvent(anchor, id.getVirtualChildIntId(), 3250 AutofillCallback.EVENT_INPUT_HIDDEN); 3251 } else { 3252 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); 3253 } 3254 } 3255 } 3256 notifyDisableAutofill(long disableDuration, ComponentName componentName)3257 private void notifyDisableAutofill(long disableDuration, ComponentName componentName) { 3258 synchronized (mLock) { 3259 if (mOptions == null) { 3260 return; 3261 } 3262 long expiration = SystemClock.elapsedRealtime() + disableDuration; 3263 // Protect it against overflow 3264 if (expiration < 0) { 3265 expiration = Long.MAX_VALUE; 3266 } 3267 if (componentName != null) { 3268 if (mOptions.disabledActivities == null) { 3269 mOptions.disabledActivities = new ArrayMap<>(); 3270 } 3271 mOptions.disabledActivities.put(componentName.flattenToString(), expiration); 3272 } else { 3273 mOptions.appDisabledExpiration = expiration; 3274 } 3275 } 3276 } 3277 notifyReenableAutofill()3278 void notifyReenableAutofill() { 3279 synchronized (mLock) { 3280 if (mOptions == null) { 3281 return; 3282 } 3283 mOptions.appDisabledExpiration = 0; 3284 mOptions.disabledActivities = null; 3285 } 3286 } 3287 notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState)3288 private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 3289 if (sVerbose) { 3290 Log.v(TAG, "notifyNoFillUi(): sessionFinishedState=" + sessionFinishedState); 3291 } 3292 final View anchor = findView(id); 3293 if (anchor == null) { 3294 return; 3295 } 3296 3297 notifyCallback(sessionId, id, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 3298 3299 if (sessionFinishedState != STATE_UNKNOWN) { 3300 // Callback call was "hijacked" to also update the session state. 3301 setSessionFinished(sessionFinishedState, /* autofillableIds= */ null); 3302 } 3303 } 3304 notifyCallback( int sessionId, AutofillId id, @AutofillCallback.AutofillEventType int event)3305 private void notifyCallback( 3306 int sessionId, AutofillId id, @AutofillCallback.AutofillEventType int event) { 3307 if (sVerbose) { 3308 Log.v(TAG, "notifyCallback(): sessionId=" + sessionId + ", autofillId=" + id 3309 + ", event=" + event); 3310 } 3311 final View anchor = findView(id); 3312 if (anchor == null) { 3313 return; 3314 } 3315 3316 AutofillCallback callback = null; 3317 synchronized (mLock) { 3318 if (mSessionId == sessionId && getClient() != null) { 3319 callback = mCallback; 3320 } 3321 } 3322 3323 if (callback != null) { 3324 if (id.isVirtualInt()) { 3325 callback.onAutofillEvent( 3326 anchor, id.getVirtualChildIntId(), event); 3327 } else { 3328 callback.onAutofillEvent(anchor, event); 3329 } 3330 } 3331 } 3332 3333 /** 3334 * Find a single view by its id. 3335 * 3336 * @param autofillId The autofill id of the view 3337 * 3338 * @return The view or {@code null} if view was not found 3339 */ findView(@onNull AutofillId autofillId)3340 private View findView(@NonNull AutofillId autofillId) { 3341 final AutofillClient client = getClient(); 3342 if (client != null) { 3343 return client.autofillClientFindViewByAutofillIdTraversal(autofillId); 3344 } 3345 return null; 3346 } 3347 3348 /** @hide */ hasAutofillFeature()3349 public boolean hasAutofillFeature() { 3350 return mService != null; 3351 } 3352 3353 /** @hide */ onPendingSaveUi(int operation, IBinder token)3354 public void onPendingSaveUi(int operation, IBinder token) { 3355 if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 3356 3357 synchronized (mLock) { 3358 try { 3359 mService.onPendingSaveUi(operation, token); 3360 } catch (RemoteException e) { 3361 Log.e(TAG, "Error in onPendingSaveUi: ", e); 3362 } 3363 } 3364 } 3365 3366 /** @hide */ dump(String outerPrefix, PrintWriter pw)3367 public void dump(String outerPrefix, PrintWriter pw) { 3368 pw.print(outerPrefix); pw.println("AutofillManager:"); 3369 final String pfx = outerPrefix + " "; 3370 pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); 3371 pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); 3372 pw.print(pfx); pw.print("context: "); pw.println(mContext); 3373 pw.print(pfx); pw.print("service client: "); pw.println(mServiceClient); 3374 final AutofillClient client = getClient(); 3375 if (client != null) { 3376 pw.print(pfx); pw.print("client: "); pw.print(client); 3377 pw.print(" ("); pw.print(client.autofillClientGetActivityToken()); pw.println(')'); 3378 } 3379 pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); 3380 pw.print(pfx); pw.print("enabledAugmentedOnly: "); pw.println(mForAugmentedAutofillOnly); 3381 pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); 3382 pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); 3383 pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); 3384 pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); 3385 pw.print(pfx); pw.print("id of last fill UI shown: "); pw.println(mIdShownFillUi); 3386 pw.print(pfx); pw.print("tracked views: "); 3387 if (mTrackedViews == null) { 3388 pw.println("null"); 3389 } else { 3390 final String pfx2 = pfx + " "; 3391 pw.println(); 3392 pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds); 3393 pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); 3394 } 3395 pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); 3396 pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds); 3397 if (mEnteredForAugmentedAutofillIds != null) { 3398 pw.print(pfx); pw.print("entered ids for augmented autofill: "); 3399 pw.println(mEnteredForAugmentedAutofillIds); 3400 } 3401 if (mForAugmentedAutofillOnly) { 3402 pw.print(pfx); pw.println("For Augmented Autofill Only"); 3403 } 3404 pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); 3405 pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); 3406 if (mOptions != null) { 3407 pw.print(pfx); pw.print("options: "); mOptions.dumpShort(pw); pw.println(); 3408 } 3409 pw.print(pfx); pw.print("compat mode enabled: "); 3410 synchronized (mLock) { 3411 pw.print(pfx); pw.print("fill dialog enabled: "); pw.println(mIsFillDialogEnabled); 3412 pw.print(pfx); pw.print("fill dialog enabled hints: "); 3413 pw.println(Arrays.toString(mFillDialogEnabledHints)); 3414 if (mCompatibilityBridge != null) { 3415 final String pfx2 = pfx + " "; 3416 pw.println("true"); 3417 pw.print(pfx2); pw.print("windowId: "); 3418 pw.println(mCompatibilityBridge.mFocusedWindowId); 3419 pw.print(pfx2); pw.print("nodeId: "); 3420 pw.println(mCompatibilityBridge.mFocusedNodeId); 3421 pw.print(pfx2); pw.print("virtualId: "); 3422 pw.println(AccessibilityNodeInfo 3423 .getVirtualDescendantId(mCompatibilityBridge.mFocusedNodeId)); 3424 pw.print(pfx2); pw.print("focusedBounds: "); 3425 pw.println(mCompatibilityBridge.mFocusedBounds); 3426 } else { 3427 pw.println("false"); 3428 } 3429 } 3430 pw.print(pfx); pw.print("debug: "); pw.print(sDebug); 3431 pw.print(" verbose: "); pw.println(sVerbose); 3432 } 3433 3434 @GuardedBy("mLock") getStateAsStringLocked()3435 private String getStateAsStringLocked() { 3436 return getStateAsString(mState); 3437 } 3438 3439 @NonNull getStateAsString(int state)3440 private static String getStateAsString(int state) { 3441 switch (state) { 3442 case STATE_UNKNOWN: 3443 return "UNKNOWN"; 3444 case STATE_ACTIVE: 3445 return "ACTIVE"; 3446 case STATE_FINISHED: 3447 return "FINISHED"; 3448 case STATE_SHOWING_SAVE_UI: 3449 return "SHOWING_SAVE_UI"; 3450 case STATE_DISABLED_BY_SERVICE: 3451 return "DISABLED_BY_SERVICE"; 3452 case STATE_UNKNOWN_COMPAT_MODE: 3453 return "UNKNOWN_COMPAT_MODE"; 3454 case STATE_UNKNOWN_FAILED: 3455 return "UNKNOWN_FAILED"; 3456 default: 3457 return "INVALID:" + state; 3458 } 3459 } 3460 3461 /** @hide */ getSmartSuggestionModeToString(@martSuggestionMode int flags)3462 public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) { 3463 switch (flags) { 3464 case FLAG_SMART_SUGGESTION_OFF: 3465 return "OFF"; 3466 case FLAG_SMART_SUGGESTION_SYSTEM: 3467 return "SYSTEM"; 3468 default: 3469 return "INVALID:" + flags; 3470 } 3471 } 3472 3473 @GuardedBy("mLock") isActiveLocked()3474 private boolean isActiveLocked() { 3475 return mState == STATE_ACTIVE; 3476 } 3477 3478 @GuardedBy("mLock") isDisabledByServiceLocked()3479 private boolean isDisabledByServiceLocked() { 3480 return mState == STATE_DISABLED_BY_SERVICE; 3481 } 3482 3483 @GuardedBy("mLock") isFinishedLocked()3484 private boolean isFinishedLocked() { 3485 return mState == STATE_FINISHED; 3486 } 3487 post(Runnable runnable)3488 private void post(Runnable runnable) { 3489 final AutofillClient client = getClient(); 3490 if (client == null) { 3491 if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); 3492 return; 3493 } 3494 client.autofillClientRunOnUiThread(runnable); 3495 } 3496 setFillDialogTriggerIds(@ullable List<AutofillId> ids)3497 private void setFillDialogTriggerIds(@Nullable List<AutofillId> ids) { 3498 mFillDialogTriggerIds = ids; 3499 } 3500 3501 /** 3502 * If autofill suggestions for a 3503 * <a href="{@docRoot}reference/android/service/autofill/Dataset.html#FillDialogUI"> 3504 * dialog-style UI</a> are available for {@code view}, shows a dialog allowing the user to 3505 * select a suggestion and returns {@code true}. 3506 * <p> 3507 * The dialog may not be shown if the autofill service does not support it, if the autofill 3508 * request has not returned a response yet, if the dialog was shown previously, or if the 3509 * input method is already shown. 3510 * <p> 3511 * It is recommended apps to call this method the first time a user focuses on 3512 * an autofill-able form, and to avoid showing the input method if the dialog is shown. If 3513 * this method returns {@code false}, you should then instead show the input method (assuming 3514 * that is how the view normally handles the focus event). If the user re-focuses on the view, 3515 * you should not call this method again so as to not disrupt usage of the input method. 3516 * 3517 * @param view the view for which to show autofill suggestions. This is typically a view 3518 * receiving a focus event. The autofill suggestions shown will include content for 3519 * related views as well. 3520 * @return {@code true} if the autofill dialog is being shown 3521 */ 3522 // TODO(b/210926084): Consider whether to include the one-time show logic within this method. showAutofillDialog(@onNull View view)3523 public boolean showAutofillDialog(@NonNull View view) { 3524 Objects.requireNonNull(view); 3525 if (shouldShowAutofillDialog(view, view.getAutofillId())) { 3526 mShowAutofillDialogCalled = true; 3527 final WeakReference<View> wrView = new WeakReference<>(view); 3528 // The id matches a trigger id, this will trigger the fill dialog. 3529 post(() -> { 3530 final View v = wrView.get(); 3531 if (v != null) { 3532 notifyViewEntered(v); 3533 } 3534 }); 3535 return true; 3536 } 3537 return false; 3538 } 3539 3540 /** 3541 * If autofill suggestions for a 3542 * <a href="{@docRoot}reference/android/service/autofill/Dataset.html#FillDialogUI"> 3543 * dialog-style UI</a> are available for virtual {@code view}, shows a dialog allowing the user 3544 * to select a suggestion and returns {@code true}. 3545 * <p> 3546 * The dialog may not be shown if the autofill service does not support it, if the autofill 3547 * request has not returned a response yet, if the dialog was shown previously, or if the 3548 * input method is already shown. 3549 * <p> 3550 * It is recommended apps to call this method the first time a user focuses on 3551 * an autofill-able form, and to avoid showing the input method if the dialog is shown. If 3552 * this method returns {@code false}, you should then instead show the input method (assuming 3553 * that is how the view normally handles the focus event). If the user re-focuses on the view, 3554 * you should not call this method again so as to not disrupt usage of the input method. 3555 * 3556 * @param view the view hosting the virtual view hierarchy which is used to show autofill 3557 * suggestions. 3558 * @param virtualId id identifying the virtual view inside the host view. 3559 * @return {@code true} if the autofill dialog is being shown 3560 */ showAutofillDialog(@onNull View view, int virtualId)3561 public boolean showAutofillDialog(@NonNull View view, int virtualId) { 3562 Objects.requireNonNull(view); 3563 if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) { 3564 mShowAutofillDialogCalled = true; 3565 final WeakReference<View> wrView = new WeakReference<>(view); 3566 // The id matches a trigger id, this will trigger the fill dialog. 3567 post(() -> { 3568 final View v = wrView.get(); 3569 if (v != null) { 3570 notifyViewEntered(v, virtualId, /* bounds= */ null, /* flags= */ 0); 3571 } 3572 }); 3573 return true; 3574 } 3575 return false; 3576 } 3577 shouldShowAutofillDialog(View view, AutofillId id)3578 private boolean shouldShowAutofillDialog(View view, AutofillId id) { 3579 if (!hasFillDialogUiFeature() 3580 || mShowAutofillDialogCalled 3581 || mFillDialogTriggerIds == null 3582 || mHasCredentialField) { 3583 return false; 3584 } 3585 3586 if (getImeStateFlag(view) == FLAG_IME_SHOWING) { 3587 // IME is showing 3588 return false; 3589 } 3590 3591 final int size = mFillDialogTriggerIds.size(); 3592 for (int i = 0; i < size; i++) { 3593 AutofillId fillId = mFillDialogTriggerIds.get(i); 3594 if (fillId.equalsIgnoreSession(id)) { 3595 return true; 3596 } 3597 } 3598 return false; 3599 } 3600 3601 /** 3602 * Implementation of the accessibility based compatibility. 3603 */ 3604 private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy { 3605 @GuardedBy("mLock") 3606 private final Rect mFocusedBounds = new Rect(); 3607 @GuardedBy("mLock") 3608 private final Rect mTempBounds = new Rect(); 3609 3610 @GuardedBy("mLock") 3611 private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 3612 @GuardedBy("mLock") 3613 private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 3614 3615 // Need to report a fake service in case a11y clients check the service list 3616 @NonNull 3617 @GuardedBy("mLock") 3618 AccessibilityServiceInfo mCompatServiceInfo; 3619 CompatibilityBridge()3620 CompatibilityBridge() { 3621 final AccessibilityManager am = AccessibilityManager.getInstance(mContext); 3622 am.setAccessibilityPolicy(this); 3623 } 3624 getCompatServiceInfo()3625 private AccessibilityServiceInfo getCompatServiceInfo() { 3626 synchronized (mLock) { 3627 if (mCompatServiceInfo != null) { 3628 return mCompatServiceInfo; 3629 } 3630 final Intent intent = new Intent(); 3631 intent.setComponent(new ComponentName("android", 3632 "com.android.server.autofill.AutofillCompatAccessibilityService")); 3633 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService( 3634 intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 3635 try { 3636 mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); 3637 } catch (XmlPullParserException | IOException e) { 3638 Log.e(TAG, "Cannot find compat autofill service:" + intent); 3639 throw new IllegalStateException("Cannot find compat autofill service"); 3640 } 3641 return mCompatServiceInfo; 3642 } 3643 } 3644 3645 @Override isEnabled(boolean accessibilityEnabled)3646 public boolean isEnabled(boolean accessibilityEnabled) { 3647 return true; 3648 } 3649 3650 @Override getRelevantEventTypes(int relevantEventTypes)3651 public int getRelevantEventTypes(int relevantEventTypes) { 3652 return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED 3653 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED 3654 | AccessibilityEvent.TYPE_VIEW_CLICKED 3655 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 3656 } 3657 3658 @Override getInstalledAccessibilityServiceList( List<AccessibilityServiceInfo> installedServices)3659 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( 3660 List<AccessibilityServiceInfo> installedServices) { 3661 if (installedServices == null) { 3662 installedServices = new ArrayList<>(); 3663 } 3664 installedServices.add(getCompatServiceInfo()); 3665 return installedServices; 3666 } 3667 3668 @Override getEnabledAccessibilityServiceList( int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService)3669 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 3670 int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) { 3671 if (enabledService == null) { 3672 enabledService = new ArrayList<>(); 3673 } 3674 enabledService.add(getCompatServiceInfo()); 3675 return enabledService; 3676 } 3677 3678 @Override onAccessibilityEvent(AccessibilityEvent event, boolean accessibilityEnabled, int relevantEventTypes)3679 public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event, 3680 boolean accessibilityEnabled, int relevantEventTypes) { 3681 final int type = event.getEventType(); 3682 if (sVerbose) { 3683 // NOTE: this is waaay spammy, but that's life. 3684 Log.v(TAG, "onAccessibilityEvent(" + AccessibilityEvent.eventTypeToString(type) 3685 + "): virtualId=" 3686 + AccessibilityNodeInfo.getVirtualDescendantId(event.getSourceNodeId()) 3687 + ", client=" + getClient()); 3688 } 3689 switch (type) { 3690 case AccessibilityEvent.TYPE_VIEW_FOCUSED: { 3691 synchronized (mLock) { 3692 if (mFocusedWindowId == event.getWindowId() 3693 && mFocusedNodeId == event.getSourceNodeId()) { 3694 return event; 3695 } 3696 if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID 3697 && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) { 3698 notifyViewExited(mFocusedWindowId, mFocusedNodeId); 3699 mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 3700 mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 3701 mFocusedBounds.set(0, 0, 0, 0); 3702 } 3703 final int windowId = event.getWindowId(); 3704 final long nodeId = event.getSourceNodeId(); 3705 if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) { 3706 mFocusedWindowId = windowId; 3707 mFocusedNodeId = nodeId; 3708 } 3709 } 3710 } break; 3711 3712 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: { 3713 synchronized (mLock) { 3714 if (mFocusedWindowId == event.getWindowId() 3715 && mFocusedNodeId == event.getSourceNodeId()) { 3716 notifyValueChanged(event.getWindowId(), event.getSourceNodeId()); 3717 } 3718 } 3719 } break; 3720 3721 case AccessibilityEvent.TYPE_VIEW_CLICKED: { 3722 synchronized (mLock) { 3723 notifyViewClicked(event.getWindowId(), event.getSourceNodeId()); 3724 } 3725 } break; 3726 3727 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { 3728 final AutofillClient client = getClient(); 3729 if (client != null) { 3730 synchronized (mLock) { 3731 if (client.autofillClientIsFillUiShowing()) { 3732 notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds); 3733 } 3734 updateTrackedViewsLocked(); 3735 } 3736 } 3737 } break; 3738 } 3739 3740 return accessibilityEnabled ? event : null; 3741 } 3742 notifyViewEntered(int windowId, long nodeId, Rect focusedBounds)3743 private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) { 3744 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 3745 if (!isVirtualNode(virtualId)) { 3746 return false; 3747 } 3748 final View view = findViewByAccessibilityId(windowId, nodeId); 3749 if (view == null) { 3750 return false; 3751 } 3752 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 3753 if (node == null) { 3754 return false; 3755 } 3756 if (!node.isEditable()) { 3757 return false; 3758 } 3759 final Rect newBounds = mTempBounds; 3760 node.getBoundsInScreen(newBounds); 3761 if (newBounds.equals(focusedBounds)) { 3762 return false; 3763 } 3764 focusedBounds.set(newBounds); 3765 AutofillManager.this.notifyViewEntered(view, virtualId, newBounds); 3766 return true; 3767 } 3768 notifyViewExited(int windowId, long nodeId)3769 private void notifyViewExited(int windowId, long nodeId) { 3770 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 3771 if (!isVirtualNode(virtualId)) { 3772 return; 3773 } 3774 final View view = findViewByAccessibilityId(windowId, nodeId); 3775 if (view == null) { 3776 return; 3777 } 3778 AutofillManager.this.notifyViewExited(view, virtualId); 3779 } 3780 notifyValueChanged(int windowId, long nodeId)3781 private void notifyValueChanged(int windowId, long nodeId) { 3782 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 3783 if (!isVirtualNode(virtualId)) { 3784 return; 3785 } 3786 final View view = findViewByAccessibilityId(windowId, nodeId); 3787 if (view == null) { 3788 return; 3789 } 3790 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 3791 if (node == null) { 3792 return; 3793 } 3794 AutofillManager.this.notifyValueChanged(view, virtualId, 3795 AutofillValue.forText(node.getText())); 3796 } 3797 notifyViewClicked(int windowId, long nodeId)3798 private void notifyViewClicked(int windowId, long nodeId) { 3799 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 3800 if (!isVirtualNode(virtualId)) { 3801 return; 3802 } 3803 final View view = findViewByAccessibilityId(windowId, nodeId); 3804 if (view == null) { 3805 return; 3806 } 3807 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 3808 if (node == null) { 3809 return; 3810 } 3811 AutofillManager.this.notifyViewClicked(view, virtualId); 3812 } 3813 3814 @GuardedBy("mLock") updateTrackedViewsLocked()3815 private void updateTrackedViewsLocked() { 3816 if (mTrackedViews != null) { 3817 mTrackedViews.onVisibleForAutofillChangedLocked(); 3818 } 3819 } 3820 findViewByAccessibilityId(int windowId, long nodeId)3821 private View findViewByAccessibilityId(int windowId, long nodeId) { 3822 final AutofillClient client = getClient(); 3823 if (client == null) { 3824 return null; 3825 } 3826 final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId); 3827 return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId); 3828 } 3829 findVirtualNodeByAccessibilityId(View view, int virtualId)3830 private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) { 3831 final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 3832 if (provider == null) { 3833 return null; 3834 } 3835 return provider.createAccessibilityNodeInfo(virtualId); 3836 } 3837 isVirtualNode(int nodeId)3838 private boolean isVirtualNode(int nodeId) { 3839 return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID 3840 && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 3841 } 3842 } 3843 3844 /** 3845 * View tracking information. Once all tracked views become invisible the session is finished. 3846 */ 3847 private class TrackedViews { 3848 /** Visible tracked views */ 3849 @NonNull private final ArraySet<AutofillId> mVisibleTrackedIds; 3850 3851 /** Invisible tracked views */ 3852 @NonNull private final ArraySet<AutofillId> mInvisibleTrackedIds; 3853 3854 /** Visible tracked views for fill dialog */ 3855 @NonNull private final ArraySet<AutofillId> mVisibleDialogTrackedIds; 3856 3857 /** Invisible tracked views for fill dialog */ 3858 @NonNull private final ArraySet<AutofillId> mInvisibleDialogTrackedIds; 3859 3860 boolean mHasNewTrackedView; 3861 boolean mIsTrackedSaveView; 3862 3863 /** 3864 * Check if set is null or value is in set. 3865 * 3866 * @param set The set or null (== empty set) 3867 * @param value The value that might be in the set 3868 * 3869 * @return {@code true} iff set is not empty and value is in set 3870 */ 3871 // TODO: move to Helper as static method isInSet(@ullable ArraySet<T> set, T value)3872 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { 3873 return set != null && set.contains(value); 3874 } 3875 3876 /** 3877 * Add a value to a set. If set is null, create a new set. 3878 * 3879 * @param set The set or null (== empty set) 3880 * @param valueToAdd The value to add 3881 * 3882 * @return The set including the new value. If set was {@code null}, a set containing only 3883 * the new value. 3884 */ 3885 // TODO: move to Helper as static method 3886 @NonNull addToSet(@ullable ArraySet<T> set, T valueToAdd)3887 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { 3888 if (set == null) { 3889 set = new ArraySet<>(1); 3890 } 3891 3892 set.add(valueToAdd); 3893 3894 return set; 3895 } 3896 3897 /** 3898 * Remove a value from a set. 3899 * 3900 * @param set The set or null (== empty set) 3901 * @param valueToRemove The value to remove 3902 * 3903 * @return The set without the removed value. {@code null} if set was null, or is empty 3904 * after removal. 3905 */ 3906 // TODO: move to Helper as static method 3907 @Nullable removeFromSet(@ullable ArraySet<T> set, T valueToRemove)3908 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { 3909 if (set == null) { 3910 return null; 3911 } 3912 3913 set.remove(valueToRemove); 3914 3915 if (set.isEmpty()) { 3916 return null; 3917 } 3918 3919 return set; 3920 } 3921 3922 /** 3923 * Set the tracked views. 3924 * 3925 * @param trackedIds The views to be tracked 3926 */ TrackedViews(@ullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds)3927 TrackedViews(@Nullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds) { 3928 mVisibleTrackedIds = new ArraySet<>(); 3929 mInvisibleTrackedIds = new ArraySet<>(); 3930 if (!ArrayUtils.isEmpty(trackedIds)) { 3931 mIsTrackedSaveView = true; 3932 initialTrackedViews(trackedIds, mVisibleTrackedIds, mInvisibleTrackedIds); 3933 } 3934 3935 mVisibleDialogTrackedIds = new ArraySet<>(); 3936 mInvisibleDialogTrackedIds = new ArraySet<>(); 3937 if (!ArrayUtils.isEmpty(allTrackedIds)) { 3938 initialTrackedViews(allTrackedIds, mVisibleDialogTrackedIds, 3939 mInvisibleDialogTrackedIds); 3940 mAllTrackedViews.addAll(Arrays.asList(allTrackedIds)); 3941 } 3942 3943 if (sVerbose) { 3944 Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): " 3945 + " mVisibleTrackedIds=" + mVisibleTrackedIds 3946 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds 3947 + " allTrackedIds=" + Arrays.toString(allTrackedIds) 3948 + " mVisibleDialogTrackedIds=" + mVisibleDialogTrackedIds 3949 + " mInvisibleDialogTrackedIds=" + mInvisibleDialogTrackedIds); 3950 } 3951 3952 if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { 3953 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); 3954 } 3955 } 3956 initialTrackedViews(AutofillId[] trackedIds, @NonNull ArraySet<AutofillId> visibleSet, @NonNull ArraySet<AutofillId> invisibleSet)3957 private void initialTrackedViews(AutofillId[] trackedIds, 3958 @NonNull ArraySet<AutofillId> visibleSet, 3959 @NonNull ArraySet<AutofillId> invisibleSet) { 3960 final boolean[] isVisible; 3961 final AutofillClient client = getClient(); 3962 if (ArrayUtils.isEmpty(trackedIds) || client == null) { 3963 return; 3964 } 3965 if (client.autofillClientIsVisibleForAutofill()) { 3966 if (sVerbose) Log.v(TAG, "client is visible, check tracked ids"); 3967 isVisible = client.autofillClientGetViewVisibility(trackedIds); 3968 } else { 3969 // All false 3970 isVisible = new boolean[trackedIds.length]; 3971 } 3972 3973 final int numIds = trackedIds.length; 3974 for (int i = 0; i < numIds; i++) { 3975 final AutofillId id = trackedIds[i]; 3976 id.resetSessionId(); 3977 3978 if (isVisible[i]) { 3979 addToSet(visibleSet, id); 3980 } else { 3981 addToSet(invisibleSet, id); 3982 } 3983 } 3984 } 3985 3986 /** 3987 * Called when a {@link View view's} visibility changes. 3988 * 3989 * @param id the id of the view/virtual view whose visibility changed. 3990 * @param isVisible visible if the view is visible in the view hierarchy. 3991 */ 3992 @GuardedBy("mLock") notifyViewVisibilityChangedLocked(@onNull AutofillId id, boolean isVisible)3993 void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { 3994 if (sDebug) { 3995 Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible=" 3996 + isVisible); 3997 } 3998 3999 if (isClientVisibleForAutofillLocked()) { 4000 if (isVisible) { 4001 if (isInSet(mInvisibleTrackedIds, id)) { 4002 removeFromSet(mInvisibleTrackedIds, id); 4003 addToSet(mVisibleTrackedIds, id); 4004 } 4005 if (isInSet(mInvisibleDialogTrackedIds, id)) { 4006 removeFromSet(mInvisibleDialogTrackedIds, id); 4007 addToSet(mVisibleDialogTrackedIds, id); 4008 } 4009 } else { 4010 if (isInSet(mVisibleTrackedIds, id)) { 4011 removeFromSet(mVisibleTrackedIds, id); 4012 addToSet(mInvisibleTrackedIds, id); 4013 } 4014 if (isInSet(mVisibleDialogTrackedIds, id)) { 4015 removeFromSet(mVisibleDialogTrackedIds, id); 4016 addToSet(mInvisibleDialogTrackedIds, id); 4017 } 4018 } 4019 } 4020 4021 if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { 4022 if (sVerbose) { 4023 Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds); 4024 } 4025 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); 4026 4027 } 4028 if (mVisibleDialogTrackedIds.isEmpty()) { 4029 if (sVerbose) { 4030 Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds); 4031 } 4032 processNoVisibleTrackedAllViews(); 4033 } 4034 } 4035 4036 /** 4037 * Called once the client becomes visible. 4038 * 4039 * @see AutofillClient#autofillClientIsVisibleForAutofill() 4040 */ 4041 @GuardedBy("mLock") onVisibleForAutofillChangedLocked()4042 void onVisibleForAutofillChangedLocked() { 4043 // The visibility of the views might have changed while the client was not be visible, 4044 // hence update the visibility state for all views. 4045 AutofillClient client = getClient(); 4046 if (client != null) { 4047 if (sVerbose) { 4048 Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds 4049 + " vis=" + mVisibleTrackedIds); 4050 } 4051 4052 onVisibleForAutofillChangedInternalLocked(mVisibleTrackedIds, mInvisibleTrackedIds); 4053 onVisibleForAutofillChangedInternalLocked( 4054 mVisibleDialogTrackedIds, mInvisibleDialogTrackedIds); 4055 } 4056 4057 if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { 4058 if (sVerbose) { 4059 Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); 4060 } 4061 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); 4062 } 4063 if (mVisibleDialogTrackedIds.isEmpty()) { 4064 if (sVerbose) { 4065 Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); 4066 } 4067 processNoVisibleTrackedAllViews(); 4068 } 4069 } 4070 onVisibleForAutofillChangedInternalLocked(@onNull ArraySet<AutofillId> visibleSet, @NonNull ArraySet<AutofillId> invisibleSet)4071 void onVisibleForAutofillChangedInternalLocked(@NonNull ArraySet<AutofillId> visibleSet, 4072 @NonNull ArraySet<AutofillId> invisibleSet) { 4073 // The visibility of the views might have changed while the client was not be visible, 4074 // hence update the visibility state for all views. 4075 if (sVerbose) { 4076 Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + invisibleSet 4077 + " vis=" + visibleSet); 4078 } 4079 4080 ArraySet<AutofillId> allTrackedIds = new ArraySet<>(); 4081 allTrackedIds.addAll(visibleSet); 4082 allTrackedIds.addAll(invisibleSet); 4083 if (!allTrackedIds.isEmpty()) { 4084 visibleSet.clear(); 4085 invisibleSet.clear(); 4086 initialTrackedViews(Helper.toArray(allTrackedIds), visibleSet, invisibleSet); 4087 } 4088 } 4089 processNoVisibleTrackedAllViews()4090 private void processNoVisibleTrackedAllViews() { 4091 mShowAutofillDialogCalled = false; 4092 } 4093 checkViewState(AutofillId id)4094 void checkViewState(AutofillId id) { 4095 if (mHasNewTrackedView) { 4096 return; 4097 } 4098 // First one new tracks view 4099 mIsFillRequested.set(false); 4100 mHasNewTrackedView = true; 4101 } 4102 } 4103 4104 /** 4105 * Callback for autofill related events. 4106 * 4107 * <p>Typically used for applications that display their own "auto-complete" views, so they can 4108 * enable / disable such views when the autofill UI is shown / hidden. 4109 */ 4110 public abstract static class AutofillCallback { 4111 4112 /** @hide */ 4113 @IntDef(prefix = { "EVENT_INPUT_" }, value = { 4114 EVENT_INPUT_SHOWN, 4115 EVENT_INPUT_HIDDEN, 4116 EVENT_INPUT_UNAVAILABLE 4117 }) 4118 @Retention(RetentionPolicy.SOURCE) 4119 public @interface AutofillEventType {} 4120 4121 /** 4122 * The autofill input UI associated with the view was shown. 4123 * 4124 * <p>If the view provides its own auto-complete UI and its currently shown, it 4125 * should be hidden upon receiving this event. 4126 */ 4127 public static final int EVENT_INPUT_SHOWN = 1; 4128 4129 /** 4130 * The autofill input UI associated with the view was hidden. 4131 * 4132 * <p>If the view provides its own auto-complete UI that was hidden upon a 4133 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. 4134 */ 4135 public static final int EVENT_INPUT_HIDDEN = 2; 4136 4137 /** 4138 * The autofill input UI associated with the view isn't shown because 4139 * autofill is not available. 4140 * 4141 * <p>If the view provides its own auto-complete UI but was not displaying it 4142 * to avoid flickering, it could shown it upon receiving this event. 4143 */ 4144 public static final int EVENT_INPUT_UNAVAILABLE = 3; 4145 4146 /** 4147 * Called after a change in the autofill state associated with a view. 4148 * 4149 * @param view view associated with the change. 4150 * 4151 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 4152 */ onAutofillEvent(@onNull View view, @AutofillEventType int event)4153 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { 4154 } 4155 4156 /** 4157 * Called after a change in the autofill state associated with a virtual view. 4158 * 4159 * @param view parent view associated with the change. 4160 * @param virtualId id identifying the virtual child inside the parent view. 4161 * 4162 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 4163 */ onAutofillEvent(@onNull View view, int virtualId, @AutofillEventType int event)4164 public void onAutofillEvent(@NonNull View view, int virtualId, 4165 @AutofillEventType int event) { 4166 } 4167 } 4168 4169 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { 4170 private final WeakReference<AutofillManager> mAfm; 4171 AutofillManagerClient(AutofillManager autofillManager)4172 private AutofillManagerClient(AutofillManager autofillManager) { 4173 mAfm = new WeakReference<>(autofillManager); 4174 } 4175 4176 @Override setState(int flags)4177 public void setState(int flags) { 4178 final AutofillManager afm = mAfm.get(); 4179 if (afm != null) { 4180 afm.post(() -> afm.setState(flags)); 4181 } 4182 } 4183 4184 @Override autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, boolean hideHighlight)4185 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, 4186 boolean hideHighlight) { 4187 final AutofillManager afm = mAfm.get(); 4188 if (afm != null) { 4189 afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight)); 4190 } 4191 } 4192 4193 @Override autofillContent(int sessionId, AutofillId id, ClipData content)4194 public void autofillContent(int sessionId, AutofillId id, ClipData content) { 4195 final AutofillManager afm = mAfm.get(); 4196 if (afm != null) { 4197 afm.post(() -> afm.autofillContent(sessionId, id, content)); 4198 } 4199 } 4200 4201 @Override authenticate(int sessionId, int authenticationId, IntentSender intent, Intent fillInIntent, boolean authenticateInline)4202 public void authenticate(int sessionId, int authenticationId, IntentSender intent, 4203 Intent fillInIntent, boolean authenticateInline) { 4204 final AutofillManager afm = mAfm.get(); 4205 if (afm != null) { 4206 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent, 4207 authenticateInline)); 4208 } 4209 } 4210 4211 @Override requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)4212 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 4213 Rect anchorBounds, IAutofillWindowPresenter presenter) { 4214 final AutofillManager afm = mAfm.get(); 4215 if (afm != null) { 4216 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 4217 presenter)); 4218 } 4219 } 4220 4221 @Override requestHideFillUi(int sessionId, AutofillId id)4222 public void requestHideFillUi(int sessionId, AutofillId id) { 4223 final AutofillManager afm = mAfm.get(); 4224 if (afm != null) { 4225 afm.post(() -> afm.requestHideFillUi(id, false)); 4226 } 4227 } 4228 4229 @Override requestHideFillUiWhenDestroyed(int sessionId, AutofillId id)4230 public void requestHideFillUiWhenDestroyed(int sessionId, AutofillId id) { 4231 final AutofillManager afm = mAfm.get(); 4232 if (afm != null) { 4233 afm.post(() -> afm.requestHideFillUi(id, true)); 4234 } 4235 } 4236 4237 @Override notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState)4238 public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 4239 final AutofillManager afm = mAfm.get(); 4240 if (afm != null) { 4241 afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); 4242 } 4243 } 4244 4245 @Override notifyFillUiShown(int sessionId, AutofillId id)4246 public void notifyFillUiShown(int sessionId, AutofillId id) { 4247 final AutofillManager afm = mAfm.get(); 4248 if (afm != null) { 4249 afm.post( 4250 () -> afm.notifyCallback( 4251 sessionId, id, AutofillCallback.EVENT_INPUT_SHOWN)); 4252 } 4253 } 4254 4255 @Override notifyFillUiHidden(int sessionId, AutofillId id)4256 public void notifyFillUiHidden(int sessionId, AutofillId id) { 4257 final AutofillManager afm = mAfm.get(); 4258 if (afm != null) { 4259 afm.post( 4260 () -> afm.notifyCallback( 4261 sessionId, id, AutofillCallback.EVENT_INPUT_HIDDEN)); 4262 } 4263 } 4264 4265 @Override notifyDisableAutofill(long disableDuration, ComponentName componentName)4266 public void notifyDisableAutofill(long disableDuration, ComponentName componentName) 4267 throws RemoteException { 4268 final AutofillManager afm = mAfm.get(); 4269 if (afm != null) { 4270 afm.post(() -> afm.notifyDisableAutofill(disableDuration, componentName)); 4271 } 4272 } 4273 4274 @Override dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen)4275 public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) { 4276 final AutofillManager afm = mAfm.get(); 4277 if (afm != null) { 4278 afm.post(() -> afm.dispatchUnhandledKey(sessionId, id, fullScreen)); 4279 } 4280 } 4281 4282 @Override startIntentSender(IntentSender intentSender, Intent intent)4283 public void startIntentSender(IntentSender intentSender, Intent intent) { 4284 final AutofillManager afm = mAfm.get(); 4285 if (afm != null) { 4286 afm.post(() -> { 4287 try { 4288 afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); 4289 } catch (IntentSender.SendIntentException e) { 4290 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); 4291 } 4292 }); 4293 } 4294 } 4295 4296 @Override setTrackedViews(int sessionId, AutofillId[] ids, boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, AutofillId saveTriggerId)4297 public void setTrackedViews(int sessionId, AutofillId[] ids, 4298 boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, 4299 AutofillId saveTriggerId) { 4300 final AutofillManager afm = mAfm.get(); 4301 if (afm != null) { 4302 afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, 4303 saveOnFinish, fillableIds, saveTriggerId)); 4304 } 4305 } 4306 4307 @Override setSaveUiState(int sessionId, boolean shown)4308 public void setSaveUiState(int sessionId, boolean shown) { 4309 final AutofillManager afm = mAfm.get(); 4310 if (afm != null) { 4311 afm.post(() -> afm.setSaveUiState(sessionId, shown)); 4312 } 4313 } 4314 4315 @Override setSessionFinished(int newState, List<AutofillId> autofillableIds)4316 public void setSessionFinished(int newState, List<AutofillId> autofillableIds) { 4317 final AutofillManager afm = mAfm.get(); 4318 if (afm != null) { 4319 afm.post(() -> afm.setSessionFinished(newState, autofillableIds)); 4320 } 4321 } 4322 4323 @Override getAugmentedAutofillClient(IResultReceiver result)4324 public void getAugmentedAutofillClient(IResultReceiver result) { 4325 final AutofillManager afm = mAfm.get(); 4326 if (afm != null) { 4327 afm.post(() -> afm.getAugmentedAutofillClient(result)); 4328 } 4329 } 4330 4331 @Override requestShowSoftInput(@onNull AutofillId id)4332 public void requestShowSoftInput(@NonNull AutofillId id) { 4333 final AutofillManager afm = mAfm.get(); 4334 if (afm != null) { 4335 afm.post(() -> afm.requestShowSoftInput(id)); 4336 } 4337 } 4338 4339 @Override notifyFillDialogTriggerIds(List<AutofillId> ids)4340 public void notifyFillDialogTriggerIds(List<AutofillId> ids) { 4341 final AutofillManager afm = mAfm.get(); 4342 if (afm != null) { 4343 afm.post(() -> afm.setFillDialogTriggerIds(ids)); 4344 } 4345 } 4346 } 4347 4348 private static final class AugmentedAutofillManagerClient 4349 extends IAugmentedAutofillManagerClient.Stub { 4350 private final WeakReference<AutofillManager> mAfm; 4351 AugmentedAutofillManagerClient(AutofillManager autofillManager)4352 private AugmentedAutofillManagerClient(AutofillManager autofillManager) { 4353 mAfm = new WeakReference<>(autofillManager); 4354 } 4355 4356 @Nullable 4357 @Override getViewNodeParcelable(@onNull AutofillId id)4358 public ViewNodeParcelable getViewNodeParcelable(@NonNull AutofillId id) { 4359 final AutofillManager afm = mAfm.get(); 4360 if (afm == null) return null; 4361 4362 final View view = getView(afm, id); 4363 if (view == null) { 4364 Log.w(TAG, "getViewNodeParcelable(" + id + "): could not find view"); 4365 return null; 4366 } 4367 final ViewRootImpl root = view.getViewRootImpl(); 4368 if (root != null 4369 && (root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) == 0) { 4370 ViewNodeBuilder viewStructure = new ViewNodeBuilder(); 4371 viewStructure.setAutofillId(view.getAutofillId()); 4372 view.onProvideAutofillStructure(viewStructure, /* flags= */ 0); 4373 // TODO(b/141703532): We don't call View#onProvideAutofillVirtualStructure for 4374 // efficiency reason. But this also means we will return null for virtual views 4375 // for now. We will add a new API to fetch the view node info of the virtual 4376 // child view. 4377 ViewNode viewNode = viewStructure.getViewNode(); 4378 if (viewNode != null && id.equals(viewNode.getAutofillId())) { 4379 return new ViewNodeParcelable(viewNode); 4380 } 4381 } 4382 return null; 4383 } 4384 4385 @Override getViewCoordinates(@onNull AutofillId id)4386 public Rect getViewCoordinates(@NonNull AutofillId id) { 4387 final AutofillManager afm = mAfm.get(); 4388 if (afm == null) return null; 4389 4390 final View view = getView(afm, id); 4391 if (view == null) { 4392 return null; 4393 } 4394 final Rect windowVisibleDisplayFrame = new Rect(); 4395 view.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame); 4396 final int[] location = new int[2]; 4397 view.getLocationOnScreen(location); 4398 final Rect rect = new Rect(location[0], location[1] - windowVisibleDisplayFrame.top, 4399 location[0] + view.getWidth(), 4400 location[1] - windowVisibleDisplayFrame.top + view.getHeight()); 4401 if (sVerbose) { 4402 Log.v(TAG, "Coordinates for " + id + ": " + rect); 4403 } 4404 return rect; 4405 } 4406 4407 @Override autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, boolean hideHighlight)4408 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values, 4409 boolean hideHighlight) { 4410 final AutofillManager afm = mAfm.get(); 4411 if (afm != null) { 4412 afm.post(() -> afm.autofill(sessionId, ids, values, hideHighlight)); 4413 } 4414 } 4415 4416 @Override requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)4417 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 4418 Rect anchorBounds, IAutofillWindowPresenter presenter) { 4419 final AutofillManager afm = mAfm.get(); 4420 if (afm != null) { 4421 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 4422 presenter)); 4423 } 4424 } 4425 4426 @Override requestHideFillUi(int sessionId, AutofillId id)4427 public void requestHideFillUi(int sessionId, AutofillId id) { 4428 final AutofillManager afm = mAfm.get(); 4429 if (afm != null) { 4430 afm.post(() -> afm.requestHideFillUi(id, false)); 4431 } 4432 } 4433 4434 @Override requestAutofill(int sessionId, AutofillId id)4435 public boolean requestAutofill(int sessionId, AutofillId id) { 4436 final AutofillManager afm = mAfm.get(); 4437 if (afm == null || afm.mSessionId != sessionId) { 4438 if (sDebug) { 4439 Slog.d(TAG, "Autofill not available or sessionId doesn't match"); 4440 } 4441 return false; 4442 } 4443 final View view = getView(afm, id); 4444 if (view == null || !view.isFocused()) { 4445 if (sDebug) { 4446 Slog.d(TAG, "View not available or is not on focus"); 4447 } 4448 return false; 4449 } 4450 if (sVerbose) { 4451 Log.v(TAG, "requestAutofill() by AugmentedAutofillService."); 4452 } 4453 afm.post(() -> afm.requestAutofillFromNewSession(view)); 4454 return true; 4455 } 4456 4457 @Nullable getView(@onNull AutofillManager afm, @NonNull AutofillId id)4458 private View getView(@NonNull AutofillManager afm, @NonNull AutofillId id) { 4459 final AutofillClient client = afm.getClient(); 4460 if (client == null) { 4461 Log.w(TAG, "getView(" + id + "): no autofill client"); 4462 return null; 4463 } 4464 View view = client.autofillClientFindViewByAutofillIdTraversal(id); 4465 if (view == null) { 4466 Log.w(TAG, "getView(" + id + "): could not find view"); 4467 } 4468 return view; 4469 } 4470 } 4471 } 4472