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