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