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