AutofillManager.java revision 09571f89435f3639a747cf4a7a0705b494c7a93e
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 17package android.view.autofill; 18 19import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 20import static android.view.autofill.Helper.sDebug; 21import static android.view.autofill.Helper.sVerbose; 22 23import android.accessibilityservice.AccessibilityServiceInfo; 24import android.annotation.IntDef; 25import android.annotation.NonNull; 26import android.annotation.Nullable; 27import android.annotation.RequiresFeature; 28import android.annotation.SystemService; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentSender; 33import android.content.pm.PackageManager; 34import android.content.pm.ResolveInfo; 35import android.graphics.Rect; 36import android.metrics.LogMaker; 37import android.os.Bundle; 38import android.os.IBinder; 39import android.os.Parcelable; 40import android.os.RemoteException; 41import android.service.autofill.AutofillService; 42import android.service.autofill.FillEventHistory; 43import android.service.autofill.UserData; 44import android.util.ArrayMap; 45import android.util.ArraySet; 46import android.util.Log; 47import android.util.SparseArray; 48import android.view.Choreographer; 49import android.view.KeyEvent; 50import android.view.View; 51import android.view.accessibility.AccessibilityEvent; 52import android.view.accessibility.AccessibilityManager; 53import android.view.accessibility.AccessibilityNodeInfo; 54import android.view.accessibility.AccessibilityNodeProvider; 55import android.view.accessibility.AccessibilityWindowInfo; 56 57import com.android.internal.annotations.GuardedBy; 58import com.android.internal.logging.MetricsLogger; 59import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 60import com.android.internal.util.ArrayUtils; 61import com.android.internal.util.Preconditions; 62 63import org.xmlpull.v1.XmlPullParserException; 64 65import java.io.IOException; 66import java.io.PrintWriter; 67import java.lang.annotation.Retention; 68import java.lang.annotation.RetentionPolicy; 69import java.lang.ref.WeakReference; 70import java.util.ArrayList; 71import java.util.Arrays; 72import java.util.Collections; 73import java.util.List; 74import java.util.Objects; 75 76//TODO: use java.lang.ref.Cleaner once Android supports Java 9 77import sun.misc.Cleaner; 78 79/** 80 * The {@link AutofillManager} provides ways for apps and custom views to integrate with the 81 * Autofill Framework lifecycle. 82 * 83 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an 84 * activity context; the autofill context is created when one of the following methods is called for 85 * the first time in an activity context, and the current user has an enabled autofill service: 86 * 87 * <ul> 88 * <li>{@link #notifyViewEntered(View)} 89 * <li>{@link #notifyViewEntered(View, int, Rect)} 90 * <li>{@link #requestAutofill(View)} 91 * </ul> 92 * 93 * <p>Tipically, the context is automatically created when the first view of the activity is 94 * focused because {@code View.onFocusChanged()} indirectly calls 95 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to 96 * explicitly create it (for example, a custom view developer could offer a contextual menu action 97 * in a text-field view to let users manually request autofill). 98 * 99 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure} 100 * that represents the view hierarchy by calling 101 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views 102 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in 103 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and 104 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in 105 * the hierarchy. 106 * 107 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which 108 * parses it looking for views that can be autofilled. If the service finds such views, it returns 109 * a data structure to the Android System containing the following optional info: 110 * 111 * <ul> 112 * <li>Datasets used to autofill subsets of views in the activity. 113 * <li>Id of views that the service can save their values for future autofilling. 114 * </ul> 115 * 116 * <p>When the service returns datasets, the Android System displays an autofill dataset picker 117 * UI associated with the view, when the view is focused on and is part of a dataset. 118 * The application can be notified when the UI is shown by registering an 119 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user 120 * selects a dataset from the UI, all views present in the dataset are autofilled, through 121 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. 122 * 123 * <p>When the service returns ids of savable views, the Android System keeps track of changes 124 * made to these views, so they can be used to determine if the autofill save UI is shown later. 125 * 126 * <p>The context is then finished when one of the following occurs: 127 * 128 * <ul> 129 * <li>{@link #commit()} is called or all savable views are gone. 130 * <li>{@link #cancel()} is called. 131 * </ul> 132 * 133 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System 134 * shows an autofill save UI if the value of savable views have changed. If the user selects the 135 * option to Save, the current value of the views is then sent to the autofill service. 136 * 137 * <p>It is safe to call into its methods from any thread. 138 */ 139@SystemService(Context.AUTOFILL_MANAGER_SERVICE) 140@RequiresFeature(PackageManager.FEATURE_AUTOFILL) 141public final class AutofillManager { 142 143 private static final String TAG = "AutofillManager"; 144 145 /** 146 * Intent extra: The assist structure which captures the filled screen. 147 * 148 * <p> 149 * Type: {@link android.app.assist.AssistStructure} 150 */ 151 public static final String EXTRA_ASSIST_STRUCTURE = 152 "android.view.autofill.extra.ASSIST_STRUCTURE"; 153 154 /** 155 * Intent extra: The result of an authentication operation. It is 156 * either a fully populated {@link android.service.autofill.FillResponse} 157 * or a fully populated {@link android.service.autofill.Dataset} if 158 * a response or a dataset is being authenticated respectively. 159 * 160 * <p> 161 * Type: {@link android.service.autofill.FillResponse} or a 162 * {@link android.service.autofill.Dataset} 163 */ 164 public static final String EXTRA_AUTHENTICATION_RESULT = 165 "android.view.autofill.extra.AUTHENTICATION_RESULT"; 166 167 /** 168 * Intent extra: The optional extras provided by the 169 * {@link android.service.autofill.AutofillService}. 170 * 171 * <p>For example, when the service responds to a {@link 172 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with 173 * a {@code FillResponse} that requires authentication, the Intent that launches the 174 * service authentication will contain the Bundle set by 175 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. 176 * 177 * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service 178 * can also add this bundle to the {@link Intent} set as the 179 * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, 180 * so the bundle can be recovered later on 181 * {@link android.service.autofill.SaveRequest#getClientState()}. 182 * 183 * <p> 184 * Type: {@link android.os.Bundle} 185 */ 186 public static final String EXTRA_CLIENT_STATE = 187 "android.view.autofill.extra.CLIENT_STATE"; 188 189 /** @hide */ 190 public static final String EXTRA_RESTORE_SESSION_TOKEN = 191 "android.view.autofill.extra.RESTORE_SESSION_TOKEN"; 192 193 private static final String SESSION_ID_TAG = "android:sessionId"; 194 private static final String STATE_TAG = "android:state"; 195 private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; 196 197 /** @hide */ public static final int ACTION_START_SESSION = 1; 198 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2; 199 /** @hide */ public static final int ACTION_VIEW_EXITED = 3; 200 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; 201 202 203 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; 204 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; 205 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; 206 207 /** Which bits in an authentication id are used for the dataset id */ 208 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF; 209 /** How many bits in an authentication id are used for the dataset id */ 210 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16; 211 /** @hide The index for an undefined data set */ 212 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF; 213 214 /** 215 * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI. 216 * 217 * @hide 218 */ 219 public static final int PENDING_UI_OPERATION_CANCEL = 1; 220 221 /** 222 * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI. 223 * 224 * @hide 225 */ 226 public static final int PENDING_UI_OPERATION_RESTORE = 2; 227 228 /** 229 * Initial state of the autofill context, set when there is no session (i.e., when 230 * {@link #mSessionId} is {@link #NO_SESSION}). 231 * 232 * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to 233 * the server. 234 * 235 * @hide 236 */ 237 public static final int STATE_UNKNOWN = 0; 238 239 /** 240 * State where the autofill context hasn't been {@link #commit() finished} nor 241 * {@link #cancel() canceled} yet. 242 * 243 * @hide 244 */ 245 public static final int STATE_ACTIVE = 1; 246 247 /** 248 * State where the autofill context was finished by the server because the autofill 249 * service could not autofill the activity. 250 * 251 * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, 252 * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). 253 * 254 * @hide 255 */ 256 public static final int STATE_FINISHED = 2; 257 258 /** 259 * State where the autofill context has been {@link #commit() finished} but the server still has 260 * a session because the Save UI hasn't been dismissed yet. 261 * 262 * @hide 263 */ 264 public static final int STATE_SHOWING_SAVE_UI = 3; 265 266 /** 267 * State where the autofill is disabled because the service cannot autofill the activity at all. 268 * 269 * <p>In this state, every call is ignored, even {@link #requestAutofill(View)} 270 * (and {@link #requestAutofill(View, int, Rect)}). 271 * 272 * @hide 273 */ 274 public static final int STATE_DISABLED_BY_SERVICE = 4; 275 276 /** 277 * Timeout in ms for calls to the field classification service. 278 * @hide 279 */ 280 public static final int FC_SERVICE_TIMEOUT = 5000; 281 282 /** 283 * Makes an authentication id from a request id and a dataset id. 284 * 285 * @param requestId The request id. 286 * @param datasetId The dataset id. 287 * @return The authentication id. 288 * @hide 289 */ 290 public static int makeAuthenticationId(int requestId, int datasetId) { 291 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT) 292 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK); 293 } 294 295 /** 296 * Gets the request id from an authentication id. 297 * 298 * @param authRequestId The authentication id. 299 * @return The request id. 300 * @hide 301 */ 302 public static int getRequestIdFromAuthenticationId(int authRequestId) { 303 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT); 304 } 305 306 /** 307 * Gets the dataset id from an authentication id. 308 * 309 * @param authRequestId The authentication id. 310 * @return The dataset id. 311 * @hide 312 */ 313 public static int getDatasetIdFromAuthenticationId(int authRequestId) { 314 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK); 315 } 316 317 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 318 319 /** 320 * There is currently no session running. 321 * {@hide} 322 */ 323 public static final int NO_SESSION = Integer.MIN_VALUE; 324 325 private final IAutoFillManager mService; 326 327 private final Object mLock = new Object(); 328 329 @GuardedBy("mLock") 330 private IAutoFillManagerClient mServiceClient; 331 332 @GuardedBy("mLock") 333 private Cleaner mServiceClientCleaner; 334 335 @GuardedBy("mLock") 336 private AutofillCallback mCallback; 337 338 private final Context mContext; 339 340 @GuardedBy("mLock") 341 private int mSessionId = NO_SESSION; 342 343 @GuardedBy("mLock") 344 private int mState = STATE_UNKNOWN; 345 346 @GuardedBy("mLock") 347 private boolean mEnabled; 348 349 /** If a view changes to this mapping the autofill operation was successful */ 350 @GuardedBy("mLock") 351 @Nullable private ParcelableMap mLastAutofilledData; 352 353 /** If view tracking is enabled, contains the tracking state */ 354 @GuardedBy("mLock") 355 @Nullable private TrackedViews mTrackedViews; 356 357 /** Views that are only tracked because they are fillable and could be anchoring the UI. */ 358 @GuardedBy("mLock") 359 @Nullable private ArraySet<AutofillId> mFillableIds; 360 361 /** 362 * Views that were already "entered" - if they're entered again when the session is not active, 363 * they're ignored 364 * */ 365 @GuardedBy("mLock") 366 @Nullable private ArraySet<AutofillId> mEnteredIds; 367 368 /** If set, session is commited when the field is clicked. */ 369 @GuardedBy("mLock") 370 @Nullable private AutofillId mSaveTriggerId; 371 372 /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ 373 @GuardedBy("mLock") 374 private boolean mOnInvisibleCalled; 375 376 /** If set, session is commited when the activity is finished; otherwise session is canceled. */ 377 @GuardedBy("mLock") 378 private boolean mSaveOnFinish; 379 380 /** If compatibility mode is enabled - this is a bridge to interact with a11y */ 381 @GuardedBy("mLock") 382 private CompatibilityBridge mCompatibilityBridge; 383 384 /** @hide */ 385 public interface AutofillClient { 386 /** 387 * Asks the client to start an authentication flow. 388 * 389 * @param authenticationId A unique id of the authentication operation. 390 * @param intent The authentication intent. 391 * @param fillInIntent The authentication fill-in intent. 392 */ 393 void autofillClientAuthenticate(int authenticationId, IntentSender intent, 394 Intent fillInIntent); 395 396 /** 397 * Tells the client this manager has state to be reset. 398 */ 399 void autofillClientResetableStateAvailable(); 400 401 /** 402 * Request showing the autofill UI. 403 * 404 * @param anchor The real view the UI needs to anchor to. 405 * @param width The width of the fill UI content. 406 * @param height The height of the fill UI content. 407 * @param virtualBounds The bounds of the virtual decendant of the anchor. 408 * @param presenter The presenter that controls the fill UI window. 409 * @return Whether the UI was shown. 410 */ 411 boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height, 412 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter); 413 414 /** 415 * Dispatch unhandled keyevent from Autofill window 416 * @param anchor The real view the UI needs to anchor to. 417 * @param keyEvent Unhandled KeyEvent from autofill window. 418 */ 419 void autofillClientDispatchUnhandledKey(@NonNull View anchor, @NonNull KeyEvent keyEvent); 420 421 /** 422 * Request hiding the autofill UI. 423 * 424 * @return Whether the UI was hidden. 425 */ 426 boolean autofillClientRequestHideFillUi(); 427 428 /** 429 * Gets whether the fill UI is currenlty being shown. 430 * 431 * @return Whether the fill UI is currently being shown 432 */ 433 boolean autofillClientIsFillUiShowing(); 434 435 /** 436 * Checks if views are currently attached and visible. 437 * 438 * @return And array with {@code true} iff the view is attached or visible 439 */ 440 @NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds); 441 442 /** 443 * Checks is the client is currently visible as understood by autofill. 444 * 445 * @return {@code true} if the client is currently visible 446 */ 447 boolean autofillClientIsVisibleForAutofill(); 448 449 /** 450 * Client might disable enter/exit event e.g. when activity is paused. 451 */ 452 boolean isDisablingEnterExitEventForAutofill(); 453 454 /** 455 * Finds views by traversing the hierarchies of the client. 456 * 457 * @param autofillIds The autofill ids of the views to find 458 * 459 * @return And array containing the views (empty if no views found). 460 */ 461 @NonNull View[] autofillClientFindViewsByAutofillIdTraversal( 462 @NonNull AutofillId[] autofillIds); 463 464 /** 465 * Finds a view by traversing the hierarchies of the client. 466 * 467 * @param autofillId The autofill id of the views to find 468 * 469 * @return The view, or {@code null} if not found 470 */ 471 @Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId); 472 473 /** 474 * Finds a view by a11y id in a given client window. 475 * 476 * @param viewId The accessibility id of the views to find 477 * @param windowId The accessibility window id where to search 478 * 479 * @return The view, or {@code null} if not found 480 */ 481 @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId); 482 483 /** 484 * Runs the specified action on the UI thread. 485 */ 486 void autofillClientRunOnUiThread(Runnable action); 487 488 /** 489 * Gets the complete component name of this client. 490 */ 491 ComponentName autofillClientGetComponentName(); 492 493 /** 494 * Gets the activity token 495 */ 496 @Nullable IBinder autofillClientGetActivityToken(); 497 498 /** 499 * @return Whether compatibility mode is enabled. 500 */ 501 boolean autofillIsCompatibilityModeEnabled(); 502 } 503 504 /** 505 * @hide 506 */ 507 public AutofillManager(Context context, IAutoFillManager service) { 508 mContext = Preconditions.checkNotNull(context, "context cannot be null"); 509 mService = service; 510 } 511 512 /** 513 * @hide 514 */ 515 public void enableCompatibilityMode() { 516 synchronized (mLock) { 517 // The accessibility manager is a singleton so we may need to plug 518 // different bridge based on which activity is currently focused 519 // in the current process. Since compat would be rarely used, just 520 // create and register a new instance every time. 521 mCompatibilityBridge = new CompatibilityBridge(); 522 } 523 } 524 525 /** 526 * Restore state after activity lifecycle 527 * 528 * @param savedInstanceState The state to be restored 529 * 530 * {@hide} 531 */ 532 public void onCreate(Bundle savedInstanceState) { 533 if (!hasAutofillFeature()) { 534 return; 535 } 536 synchronized (mLock) { 537 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); 538 539 if (isActiveLocked()) { 540 Log.w(TAG, "New session was started before onCreate()"); 541 return; 542 } 543 544 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); 545 mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); 546 547 if (mSessionId != NO_SESSION) { 548 ensureServiceClientAddedIfNeededLocked(); 549 550 final AutofillClient client = getClient(); 551 if (client != null) { 552 try { 553 final boolean sessionWasRestored = mService.restoreSession(mSessionId, 554 client.autofillClientGetActivityToken(), 555 mServiceClient.asBinder()); 556 557 if (!sessionWasRestored) { 558 Log.w(TAG, "Session " + mSessionId + " could not be restored"); 559 mSessionId = NO_SESSION; 560 mState = STATE_UNKNOWN; 561 } else { 562 if (sDebug) { 563 Log.d(TAG, "session " + mSessionId + " was restored"); 564 } 565 566 client.autofillClientResetableStateAvailable(); 567 } 568 } catch (RemoteException e) { 569 Log.e(TAG, "Could not figure out if there was an autofill session", e); 570 } 571 } 572 } 573 } 574 } 575 576 /** 577 * Called once the client becomes visible. 578 * 579 * @see AutofillClient#autofillClientIsVisibleForAutofill() 580 * 581 * {@hide} 582 */ 583 public void onVisibleForAutofill() { 584 // This gets called when the client just got visible at which point the visibility 585 // of the tracked views may not have been computed (due to a pending layout, etc). 586 // While generally we have no way to know when the UI has settled. We will evaluate 587 // the tracked views state at the end of next frame to guarantee that everything 588 // that may need to be laid out is laid out. 589 Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { 590 synchronized (mLock) { 591 if (mEnabled && isActiveLocked() && mTrackedViews != null) { 592 mTrackedViews.onVisibleForAutofillChangedLocked(); 593 } 594 } 595 }, null); 596 } 597 598 /** 599 * Called once the client becomes invisible. 600 * 601 * @see AutofillClient#autofillClientIsVisibleForAutofill() 602 * 603 * {@hide} 604 */ 605 public void onInvisibleForAutofill() { 606 synchronized (mLock) { 607 mOnInvisibleCalled = true; 608 } 609 } 610 611 /** 612 * Save state before activity lifecycle 613 * 614 * @param outState Place to store the state 615 * 616 * {@hide} 617 */ 618 public void onSaveInstanceState(Bundle outState) { 619 if (!hasAutofillFeature()) { 620 return; 621 } 622 synchronized (mLock) { 623 if (mSessionId != NO_SESSION) { 624 outState.putInt(SESSION_ID_TAG, mSessionId); 625 } 626 if (mState != STATE_UNKNOWN) { 627 outState.putInt(STATE_TAG, mState); 628 } 629 if (mLastAutofilledData != null) { 630 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); 631 } 632 } 633 } 634 635 /** 636 * @hide 637 */ 638 @GuardedBy("mLock") 639 public boolean isCompatibilityModeEnabledLocked() { 640 return mCompatibilityBridge != null; 641 } 642 643 /** 644 * Checks whether autofill is enabled for the current user. 645 * 646 * <p>Typically used to determine whether the option to explicitly request autofill should 647 * be offered - see {@link #requestAutofill(View)}. 648 * 649 * @return whether autofill is enabled for the current user. 650 */ 651 public boolean isEnabled() { 652 if (!hasAutofillFeature()) { 653 return false; 654 } 655 synchronized (mLock) { 656 if (isDisabledByServiceLocked()) { 657 return false; 658 } 659 ensureServiceClientAddedIfNeededLocked(); 660 return mEnabled; 661 } 662 } 663 664 /** 665 * Should always be called from {@link AutofillService#getFillEventHistory()}. 666 * 667 * @hide 668 */ 669 @Nullable public FillEventHistory getFillEventHistory() { 670 try { 671 return mService.getFillEventHistory(); 672 } catch (RemoteException e) { 673 e.rethrowFromSystemServer(); 674 return null; 675 } 676 } 677 678 /** 679 * Explicitly requests a new autofill context. 680 * 681 * <p>Normally, the autofill context is automatically started if necessary when 682 * {@link #notifyViewEntered(View)} is called, but this method should be used in the 683 * cases where it must be explicitly started. For example, when the view offers an AUTOFILL 684 * option on its contextual overflow menu, and the user selects it. 685 * 686 * @param view view requesting the new autofill context. 687 */ 688 public void requestAutofill(@NonNull View view) { 689 notifyViewEntered(view, FLAG_MANUAL_REQUEST); 690 } 691 692 /** 693 * Explicitly requests a new autofill context for virtual views. 694 * 695 * <p>Normally, the autofill context is automatically started if necessary when 696 * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the 697 * cases where it must be explicitly started. For example, when the virtual view offers an 698 * AUTOFILL option on its contextual overflow menu, and the user selects it. 699 * 700 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 701 * parent view uses {@code bounds} to draw the virtual view inside its Canvas, 702 * the absolute bounds could be calculated by: 703 * 704 * <pre class="prettyprint"> 705 * int offset[] = new int[2]; 706 * getLocationOnScreen(offset); 707 * Rect absBounds = new Rect(bounds.left + offset[0], 708 * bounds.top + offset[1], 709 * bounds.right + offset[0], bounds.bottom + offset[1]); 710 * </pre> 711 * 712 * @param view the virtual view parent. 713 * @param virtualId id identifying the virtual child inside the parent view. 714 * @param absBounds absolute boundaries of the virtual view in the screen. 715 */ 716 public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 717 notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST); 718 } 719 720 /** 721 * Called when a {@link View} that supports autofill is entered. 722 * 723 * @param view {@link View} that was entered. 724 */ 725 public void notifyViewEntered(@NonNull View view) { 726 notifyViewEntered(view, 0); 727 } 728 729 @GuardedBy("mLock") 730 private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) { 731 if (isDisabledByServiceLocked()) { 732 if (sVerbose) { 733 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 734 + ") on state " + getStateAsStringLocked() + " because disabled by svc"); 735 } 736 return true; 737 } 738 if (isFinishedLocked()) { 739 // Session already finished: ignore if automatic request and view already entered 740 if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null 741 && mEnteredIds.contains(id)) { 742 if (sVerbose) { 743 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 744 + ") on state " + getStateAsStringLocked() 745 + " because view was already entered: " + mEnteredIds); 746 } 747 return true; 748 } 749 } 750 if (sVerbose) { 751 Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + id 752 + ", state " + getStateAsStringLocked() + ", enteredIds=" + mEnteredIds); 753 } 754 return false; 755 } 756 757 private boolean isClientVisibleForAutofillLocked() { 758 final AutofillClient client = getClient(); 759 return client != null && client.autofillClientIsVisibleForAutofill(); 760 } 761 762 private boolean isClientDisablingEnterExitEvent() { 763 final AutofillClient client = getClient(); 764 return client != null && client.isDisablingEnterExitEventForAutofill(); 765 } 766 767 private void notifyViewEntered(@NonNull View view, int flags) { 768 if (!hasAutofillFeature()) { 769 return; 770 } 771 AutofillCallback callback; 772 synchronized (mLock) { 773 callback = notifyViewEnteredLocked(view, flags); 774 } 775 776 if (callback != null) { 777 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 778 } 779 } 780 781 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ 782 @GuardedBy("mLock") 783 private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { 784 final AutofillId id = getAutofillId(view); 785 if (shouldIgnoreViewEnteredLocked(id, flags)) return null; 786 787 AutofillCallback callback = null; 788 789 ensureServiceClientAddedIfNeededLocked(); 790 791 if (!mEnabled) { 792 if (mCallback != null) { 793 callback = mCallback; 794 } 795 } else { 796 // don't notify entered when Activity is already in background 797 if (!isClientDisablingEnterExitEvent()) { 798 final AutofillValue value = view.getAutofillValue(); 799 800 if (!isActiveLocked()) { 801 // Starts new session. 802 startSessionLocked(id, null, value, flags); 803 } else { 804 // Update focus on existing session. 805 updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags); 806 } 807 addEnteredIdLocked(id); 808 } 809 } 810 return callback; 811 } 812 813 /** 814 * Called when a {@link View} that supports autofill is exited. 815 * 816 * @param view {@link View} that was exited. 817 */ 818 public void notifyViewExited(@NonNull View view) { 819 if (!hasAutofillFeature()) { 820 return; 821 } 822 synchronized (mLock) { 823 notifyViewExitedLocked(view); 824 } 825 } 826 827 @GuardedBy("mLock") 828 void notifyViewExitedLocked(@NonNull View view) { 829 ensureServiceClientAddedIfNeededLocked(); 830 831 if (mEnabled && isActiveLocked()) { 832 // dont notify exited when Activity is already in background 833 if (!isClientDisablingEnterExitEvent()) { 834 final AutofillId id = getAutofillId(view); 835 836 // Update focus on existing session. 837 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 838 } 839 } 840 } 841 842 /** 843 * Called when a {@link View view's} visibility changed. 844 * 845 * @param view {@link View} that was exited. 846 * @param isVisible visible if the view is visible in the view hierarchy. 847 */ 848 public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) { 849 notifyViewVisibilityChangedInternal(view, 0, isVisible, false); 850 } 851 852 /** 853 * Called when a virtual view's visibility changed. 854 * 855 * @param view {@link View} that was exited. 856 * @param virtualId id identifying the virtual child inside the parent view. 857 * @param isVisible visible if the view is visible in the view hierarchy. 858 */ 859 public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) { 860 notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true); 861 } 862 863 /** 864 * Called when a view/virtual view's visibility changed. 865 * 866 * @param view {@link View} that was exited. 867 * @param virtualId id identifying the virtual child inside the parent view. 868 * @param isVisible visible if the view is visible in the view hierarchy. 869 * @param virtual Whether the view is virtual. 870 */ 871 private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId, 872 boolean isVisible, boolean virtual) { 873 synchronized (mLock) { 874 if (mEnabled && isActiveLocked()) { 875 final AutofillId id = virtual ? getAutofillId(view, virtualId) 876 : view.getAutofillId(); 877 if (!isVisible && mFillableIds != null) { 878 if (mFillableIds.contains(id)) { 879 if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible"); 880 requestHideFillUi(id, view); 881 } 882 } 883 if (mTrackedViews != null) { 884 mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); 885 } 886 } 887 } 888 } 889 890 /** 891 * Called when a virtual view that supports autofill is entered. 892 * 893 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 894 * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas, 895 * the absolute bounds could be calculated by: 896 * 897 * <pre class="prettyprint"> 898 * int offset[] = new int[2]; 899 * getLocationOnScreen(offset); 900 * Rect absBounds = new Rect(bounds.left + offset[0], 901 * bounds.top + offset[1], 902 * bounds.right + offset[0], bounds.bottom + offset[1]); 903 * </pre> 904 * 905 * @param view the virtual view parent. 906 * @param virtualId id identifying the virtual child inside the parent view. 907 * @param absBounds absolute boundaries of the virtual view in the screen. 908 */ 909 public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 910 notifyViewEntered(view, virtualId, absBounds, 0); 911 } 912 913 private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) { 914 if (!hasAutofillFeature()) { 915 return; 916 } 917 AutofillCallback callback; 918 synchronized (mLock) { 919 callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); 920 } 921 922 if (callback != null) { 923 callback.onAutofillEvent(view, virtualId, 924 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 925 } 926 } 927 928 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ 929 @GuardedBy("mLock") 930 private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, 931 int flags) { 932 final AutofillId id = getAutofillId(view, virtualId); 933 AutofillCallback callback = null; 934 if (shouldIgnoreViewEnteredLocked(id, flags)) return callback; 935 936 ensureServiceClientAddedIfNeededLocked(); 937 938 if (!mEnabled) { 939 if (mCallback != null) { 940 callback = mCallback; 941 } 942 } else { 943 // don't notify entered when Activity is already in background 944 if (!isClientDisablingEnterExitEvent()) { 945 if (!isActiveLocked()) { 946 // Starts new session. 947 startSessionLocked(id, bounds, null, flags); 948 } else { 949 // Update focus on existing session. 950 updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags); 951 } 952 addEnteredIdLocked(id); 953 } 954 } 955 return callback; 956 } 957 958 @GuardedBy("mLock") 959 private void addEnteredIdLocked(@NonNull AutofillId id) { 960 if (mEnteredIds == null) { 961 mEnteredIds = new ArraySet<>(1); 962 } 963 mEnteredIds.add(id); 964 } 965 966 /** 967 * Called when a virtual view that supports autofill is exited. 968 * 969 * @param view the virtual view parent. 970 * @param virtualId id identifying the virtual child inside the parent view. 971 */ 972 public void notifyViewExited(@NonNull View view, int virtualId) { 973 if (!hasAutofillFeature()) { 974 return; 975 } 976 synchronized (mLock) { 977 notifyViewExitedLocked(view, virtualId); 978 } 979 } 980 981 @GuardedBy("mLock") 982 private void notifyViewExitedLocked(@NonNull View view, int virtualId) { 983 ensureServiceClientAddedIfNeededLocked(); 984 985 if (mEnabled && isActiveLocked()) { 986 // don't notify exited when Activity is already in background 987 if (!isClientDisablingEnterExitEvent()) { 988 final AutofillId id = getAutofillId(view, virtualId); 989 990 // Update focus on existing session. 991 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 992 } 993 } 994 } 995 996 /** 997 * Called to indicate the value of an autofillable {@link View} changed. 998 * 999 * @param view view whose value changed. 1000 */ 1001 public void notifyValueChanged(View view) { 1002 if (!hasAutofillFeature()) { 1003 return; 1004 } 1005 AutofillId id = null; 1006 boolean valueWasRead = false; 1007 AutofillValue value = null; 1008 1009 synchronized (mLock) { 1010 // If the session is gone some fields might still be highlighted, hence we have to 1011 // remove the isAutofilled property even if no sessions are active. 1012 if (mLastAutofilledData == null) { 1013 view.setAutofilled(false); 1014 } else { 1015 id = getAutofillId(view); 1016 if (mLastAutofilledData.containsKey(id)) { 1017 value = view.getAutofillValue(); 1018 valueWasRead = true; 1019 1020 if (Objects.equals(mLastAutofilledData.get(id), value)) { 1021 view.setAutofilled(true); 1022 } else { 1023 view.setAutofilled(false); 1024 mLastAutofilledData.remove(id); 1025 } 1026 } else { 1027 view.setAutofilled(false); 1028 } 1029 } 1030 1031 if (!mEnabled || !isActiveLocked()) { 1032 if (sVerbose) { 1033 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() 1034 + "): ignoring on state " + getStateAsStringLocked()); 1035 } 1036 return; 1037 } 1038 1039 if (id == null) { 1040 id = getAutofillId(view); 1041 } 1042 1043 if (!valueWasRead) { 1044 value = view.getAutofillValue(); 1045 } 1046 1047 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 1048 } 1049 } 1050 1051 /** 1052 * Called to indicate the value of an autofillable virtual view has changed. 1053 * 1054 * @param view the virtual view parent. 1055 * @param virtualId id identifying the virtual child inside the parent view. 1056 * @param value new value of the child. 1057 */ 1058 public void notifyValueChanged(View view, int virtualId, AutofillValue value) { 1059 if (!hasAutofillFeature()) { 1060 return; 1061 } 1062 synchronized (mLock) { 1063 if (!mEnabled || !isActiveLocked()) { 1064 if (sVerbose) { 1065 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId 1066 + "): ignoring on state " + getStateAsStringLocked()); 1067 } 1068 return; 1069 } 1070 1071 final AutofillId id = getAutofillId(view, virtualId); 1072 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 1073 } 1074 } 1075 1076 /** 1077 * Called to indicate a {@link View} is clicked. 1078 * 1079 * @param view view that has been clicked. 1080 */ 1081 public void notifyViewClicked(@NonNull View view) { 1082 notifyViewClicked(view.getAutofillId()); 1083 } 1084 1085 /** 1086 * Called to indicate a virtual view has been clicked. 1087 * 1088 * @param view the virtual view parent. 1089 * @param virtualId id identifying the virtual child inside the parent view. 1090 */ 1091 public void notifyViewClicked(@NonNull View view, int virtualId) { 1092 notifyViewClicked(getAutofillId(view, virtualId)); 1093 } 1094 1095 private void notifyViewClicked(AutofillId id) { 1096 if (!hasAutofillFeature()) { 1097 return; 1098 } 1099 if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); 1100 1101 synchronized (mLock) { 1102 if (!mEnabled || !isActiveLocked()) { 1103 return; 1104 } 1105 if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { 1106 if (sDebug) Log.d(TAG, "triggering commit by click of " + id); 1107 commitLocked(); 1108 mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED, 1109 mContext.getPackageName()); 1110 } 1111 } 1112 } 1113 1114 /** 1115 * Called by {@link android.app.Activity} to commit or cancel the session on finish. 1116 * 1117 * @hide 1118 */ 1119 public void onActivityFinishing() { 1120 if (!hasAutofillFeature()) { 1121 return; 1122 } 1123 synchronized (mLock) { 1124 if (mSaveOnFinish) { 1125 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling commitLocked()"); 1126 commitLocked(); 1127 } else { 1128 if (sDebug) Log.d(TAG, "onActivityFinishing(): calling cancelLocked()"); 1129 cancelLocked(); 1130 } 1131 } 1132 } 1133 1134 /** 1135 * Called to indicate the current autofill context should be commited. 1136 * 1137 * <p>This method is typically called by {@link View Views} that manage virtual views; for 1138 * example, when the view is rendering an {@code HTML} page with a form and virtual views 1139 * that represent the HTML elements, it should call this method after the form is submitted and 1140 * another page is rendered. 1141 * 1142 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 1143 * methods such as {@link android.app.Activity#finish()}. 1144 */ 1145 public void commit() { 1146 if (!hasAutofillFeature()) { 1147 return; 1148 } 1149 if (sVerbose) Log.v(TAG, "commit() called by app"); 1150 synchronized (mLock) { 1151 commitLocked(); 1152 } 1153 } 1154 1155 @GuardedBy("mLock") 1156 private void commitLocked() { 1157 if (!mEnabled && !isActiveLocked()) { 1158 return; 1159 } 1160 finishSessionLocked(); 1161 } 1162 1163 /** 1164 * Called to indicate the current autofill context should be cancelled. 1165 * 1166 * <p>This method is typically called by {@link View Views} that manage virtual views; for 1167 * example, when the view is rendering an {@code HTML} page with a form and virtual views 1168 * that represent the HTML elements, it should call this method if the user does not post the 1169 * form but moves to another form in this page. 1170 * 1171 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 1172 * methods such as {@link android.app.Activity#finish()}. 1173 */ 1174 public void cancel() { 1175 if (sVerbose) Log.v(TAG, "cancel() called by app"); 1176 if (!hasAutofillFeature()) { 1177 return; 1178 } 1179 synchronized (mLock) { 1180 cancelLocked(); 1181 } 1182 } 1183 1184 @GuardedBy("mLock") 1185 private void cancelLocked() { 1186 if (!mEnabled && !isActiveLocked()) { 1187 return; 1188 } 1189 cancelSessionLocked(); 1190 } 1191 1192 /** @hide */ 1193 public void disableOwnedAutofillServices() { 1194 disableAutofillServices(); 1195 } 1196 1197 /** 1198 * If the app calling this API has enabled autofill services they 1199 * will be disabled. 1200 */ 1201 public void disableAutofillServices() { 1202 if (!hasAutofillFeature()) { 1203 return; 1204 } 1205 try { 1206 mService.disableOwnedAutofillServices(mContext.getUserId()); 1207 } catch (RemoteException e) { 1208 throw e.rethrowFromSystemServer(); 1209 } 1210 } 1211 1212 /** 1213 * Returns {@code true} if the calling application provides a {@link AutofillService} that is 1214 * enabled for the current user, or {@code false} otherwise. 1215 */ 1216 public boolean hasEnabledAutofillServices() { 1217 if (mService == null) return false; 1218 1219 try { 1220 return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName()); 1221 } catch (RemoteException e) { 1222 throw e.rethrowFromSystemServer(); 1223 } 1224 } 1225 1226 /** 1227 * Returns the component name of the {@link AutofillService} that is enabled for the current 1228 * user. 1229 */ 1230 @Nullable 1231 public ComponentName getAutofillServiceComponentName() { 1232 if (mService == null) return null; 1233 1234 try { 1235 return mService.getAutofillServiceComponentName(); 1236 } catch (RemoteException e) { 1237 throw e.rethrowFromSystemServer(); 1238 } 1239 } 1240 1241 /** 1242 * Gets the id of the {@link UserData} used for 1243 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1244 * 1245 * <p>This method is useful when the service must check the status of the {@link UserData} in 1246 * the device without fetching the whole object. 1247 * 1248 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1249 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1250 * the user. 1251 * 1252 * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)} 1253 * or {@code null} if it was reset or if the caller currently does not have an enabled autofill 1254 * service for the user. 1255 */ 1256 @Nullable public String getUserDataId() { 1257 try { 1258 return mService.getUserDataId(); 1259 } catch (RemoteException e) { 1260 e.rethrowFromSystemServer(); 1261 return null; 1262 } 1263 } 1264 1265 /** 1266 * Gets the user data used for 1267 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1268 * 1269 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1270 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1271 * the user. 1272 * 1273 * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was 1274 * reset or if the caller currently does not have an enabled autofill service for the user. 1275 */ 1276 @Nullable public UserData getUserData() { 1277 try { 1278 return mService.getUserData(); 1279 } catch (RemoteException e) { 1280 e.rethrowFromSystemServer(); 1281 return null; 1282 } 1283 } 1284 1285 /** 1286 * Sets the {@link UserData} used for 1287 * <a href="AutofillService.html#FieldClassification">field classification</a> 1288 * 1289 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1290 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1291 * the user. 1292 */ 1293 public void setUserData(@Nullable UserData userData) { 1294 try { 1295 mService.setUserData(userData); 1296 } catch (RemoteException e) { 1297 e.rethrowFromSystemServer(); 1298 } 1299 } 1300 1301 /** 1302 * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is 1303 * enabled. 1304 * 1305 * <p>As field classification is an expensive operation, it could be disabled, either 1306 * temporarily (for example, because the service exceeded a rate-limit threshold) or 1307 * permanently (for example, because the device is a low-level device). 1308 * 1309 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1310 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1311 * the user. 1312 */ 1313 public boolean isFieldClassificationEnabled() { 1314 try { 1315 return mService.isFieldClassificationEnabled(); 1316 } catch (RemoteException e) { 1317 e.rethrowFromSystemServer(); 1318 return false; 1319 } 1320 } 1321 1322 /** 1323 * Gets the name of the default algorithm used for 1324 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1325 * 1326 * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not 1327 * set. 1328 * 1329 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1330 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1331 * the user. 1332 */ 1333 @Nullable 1334 public String getDefaultFieldClassificationAlgorithm() { 1335 try { 1336 return mService.getDefaultFieldClassificationAlgorithm(); 1337 } catch (RemoteException e) { 1338 e.rethrowFromSystemServer(); 1339 return null; 1340 } 1341 } 1342 1343 /** 1344 * Gets the name of all algorithms currently available for 1345 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1346 * 1347 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1348 * and it returns an empty list if the caller currently doesn't have an enabled autofill service 1349 * for the user. 1350 */ 1351 @NonNull 1352 public List<String> getAvailableFieldClassificationAlgorithms() { 1353 final String[] algorithms; 1354 try { 1355 algorithms = mService.getAvailableFieldClassificationAlgorithms(); 1356 return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); 1357 } catch (RemoteException e) { 1358 e.rethrowFromSystemServer(); 1359 return null; 1360 } 1361 } 1362 1363 /** 1364 * Returns {@code true} if autofill is supported by the current device and 1365 * is supported for this user. 1366 * 1367 * <p>Autofill is typically supported, but it could be unsupported in cases like: 1368 * <ol> 1369 * <li>Low-end devices. 1370 * <li>Device policy rules that forbid its usage. 1371 * </ol> 1372 */ 1373 public boolean isAutofillSupported() { 1374 if (mService == null) return false; 1375 1376 try { 1377 return mService.isServiceSupported(mContext.getUserId()); 1378 } catch (RemoteException e) { 1379 throw e.rethrowFromSystemServer(); 1380 } 1381 } 1382 1383 // Note: don't need to use locked suffix because mContext is final. 1384 private AutofillClient getClient() { 1385 final AutofillClient client = mContext.getAutofillClient(); 1386 if (client == null && sDebug) { 1387 Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " 1388 + mContext); 1389 } 1390 return client; 1391 } 1392 1393 /** @hide */ 1394 public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { 1395 if (!hasAutofillFeature()) { 1396 return; 1397 } 1398 // TODO: the result code is being ignored, so this method is not reliably 1399 // handling the cases where it's not RESULT_OK: it works fine if the service does not 1400 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the 1401 // service set the extra and returned RESULT_CANCELED... 1402 1403 if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); 1404 1405 synchronized (mLock) { 1406 if (!isActiveLocked()) { 1407 return; 1408 } 1409 // If authenticate activity closes itself during onCreate(), there is no onStop/onStart 1410 // of app activity. We enforce enter event to re-show fill ui in such case. 1411 // CTS example: 1412 // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt 1413 // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt 1414 if (!mOnInvisibleCalled && focusView != null 1415 && focusView.canNotifyAutofillEnterExitEvent()) { 1416 notifyViewExitedLocked(focusView); 1417 notifyViewEnteredLocked(focusView, 0); 1418 } 1419 if (data == null) { 1420 // data is set to null when result is not RESULT_OK 1421 return; 1422 } 1423 1424 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); 1425 final Bundle responseData = new Bundle(); 1426 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); 1427 final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); 1428 if (newClientState != null) { 1429 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); 1430 } 1431 try { 1432 mService.setAuthenticationResult(responseData, mSessionId, authenticationId, 1433 mContext.getUserId()); 1434 } catch (RemoteException e) { 1435 Log.e(TAG, "Error delivering authentication result", e); 1436 } 1437 } 1438 } 1439 1440 private static AutofillId getAutofillId(View view) { 1441 return new AutofillId(view.getAutofillViewId()); 1442 } 1443 1444 private static AutofillId getAutofillId(View parent, int virtualId) { 1445 return new AutofillId(parent.getAutofillViewId(), virtualId); 1446 } 1447 1448 @GuardedBy("mLock") 1449 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, 1450 @NonNull AutofillValue value, int flags) { 1451 if (sVerbose) { 1452 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value 1453 + ", flags=" + flags + ", state=" + getStateAsStringLocked() 1454 + ", compatMode=" + isCompatibilityModeEnabledLocked() 1455 + ", enteredIds=" + mEnteredIds); 1456 } 1457 if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { 1458 if (sVerbose) { 1459 Log.v(TAG, "not automatically starting session for " + id 1460 + " on state " + getStateAsStringLocked() + " and flags " + flags); 1461 } 1462 return; 1463 } 1464 try { 1465 final AutofillClient client = getClient(); 1466 if (client == null) return; // NOTE: getClient() already logged it.. 1467 1468 mSessionId = mService.startSession(client.autofillClientGetActivityToken(), 1469 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 1470 mCallback != null, flags, client.autofillClientGetComponentName(), 1471 isCompatibilityModeEnabledLocked()); 1472 if (mSessionId != NO_SESSION) { 1473 mState = STATE_ACTIVE; 1474 } 1475 client.autofillClientResetableStateAvailable(); 1476 } catch (RemoteException e) { 1477 throw e.rethrowFromSystemServer(); 1478 } 1479 } 1480 1481 @GuardedBy("mLock") 1482 private void finishSessionLocked() { 1483 if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); 1484 1485 if (!isActiveLocked()) return; 1486 1487 try { 1488 mService.finishSession(mSessionId, mContext.getUserId()); 1489 } catch (RemoteException e) { 1490 throw e.rethrowFromSystemServer(); 1491 } 1492 1493 resetSessionLocked(/* resetEnteredIds= */ true); 1494 } 1495 1496 @GuardedBy("mLock") 1497 private void cancelSessionLocked() { 1498 if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); 1499 1500 if (!isActiveLocked()) return; 1501 1502 try { 1503 mService.cancelSession(mSessionId, mContext.getUserId()); 1504 } catch (RemoteException e) { 1505 throw e.rethrowFromSystemServer(); 1506 } 1507 1508 resetSessionLocked(/* resetEnteredIds= */ true); 1509 } 1510 1511 @GuardedBy("mLock") 1512 private void resetSessionLocked(boolean resetEnteredIds) { 1513 mSessionId = NO_SESSION; 1514 mState = STATE_UNKNOWN; 1515 mTrackedViews = null; 1516 mFillableIds = null; 1517 mSaveTriggerId = null; 1518 if (resetEnteredIds) { 1519 mEnteredIds = null; 1520 } 1521 } 1522 1523 @GuardedBy("mLock") 1524 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, 1525 int flags) { 1526 if (sVerbose) { 1527 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds 1528 + ", value=" + value + ", action=" + action + ", flags=" + flags); 1529 } 1530 boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0; 1531 1532 try { 1533 if (restartIfNecessary) { 1534 final AutofillClient client = getClient(); 1535 if (client == null) return; // NOTE: getClient() already logd it.. 1536 1537 final int newId = mService.updateOrRestartSession( 1538 client.autofillClientGetActivityToken(), 1539 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 1540 mCallback != null, flags, client.autofillClientGetComponentName(), 1541 mSessionId, action, isCompatibilityModeEnabledLocked()); 1542 if (newId != mSessionId) { 1543 if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); 1544 mSessionId = newId; 1545 mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; 1546 client.autofillClientResetableStateAvailable(); 1547 } 1548 } else { 1549 mService.updateSession(mSessionId, id, bounds, value, action, flags, 1550 mContext.getUserId()); 1551 } 1552 1553 } catch (RemoteException e) { 1554 throw e.rethrowFromSystemServer(); 1555 } 1556 } 1557 1558 @GuardedBy("mLock") 1559 private void ensureServiceClientAddedIfNeededLocked() { 1560 if (getClient() == null) { 1561 return; 1562 } 1563 1564 if (mServiceClient == null) { 1565 mServiceClient = new AutofillManagerClient(this); 1566 try { 1567 final int userId = mContext.getUserId(); 1568 final int flags = mService.addClient(mServiceClient, userId); 1569 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; 1570 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; 1571 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; 1572 final IAutoFillManager service = mService; 1573 final IAutoFillManagerClient serviceClient = mServiceClient; 1574 mServiceClientCleaner = Cleaner.create(this, () -> { 1575 try { 1576 service.removeClient(serviceClient, userId); 1577 } catch (RemoteException e) { 1578 } 1579 }); 1580 } catch (RemoteException e) { 1581 throw e.rethrowFromSystemServer(); 1582 } 1583 } 1584 } 1585 1586 /** 1587 * Registers a {@link AutofillCallback} to receive autofill events. 1588 * 1589 * @param callback callback to receive events. 1590 */ 1591 public void registerCallback(@Nullable AutofillCallback callback) { 1592 if (!hasAutofillFeature()) { 1593 return; 1594 } 1595 synchronized (mLock) { 1596 if (callback == null) return; 1597 1598 final boolean hadCallback = mCallback != null; 1599 mCallback = callback; 1600 1601 if (!hadCallback) { 1602 try { 1603 mService.setHasCallback(mSessionId, mContext.getUserId(), true); 1604 } catch (RemoteException e) { 1605 throw e.rethrowFromSystemServer(); 1606 } 1607 } 1608 } 1609 } 1610 1611 /** 1612 * Unregisters a {@link AutofillCallback} to receive autofill events. 1613 * 1614 * @param callback callback to stop receiving events. 1615 */ 1616 public void unregisterCallback(@Nullable AutofillCallback callback) { 1617 if (!hasAutofillFeature()) { 1618 return; 1619 } 1620 synchronized (mLock) { 1621 if (callback == null || mCallback == null || callback != mCallback) return; 1622 1623 mCallback = null; 1624 1625 try { 1626 mService.setHasCallback(mSessionId, mContext.getUserId(), false); 1627 } catch (RemoteException e) { 1628 throw e.rethrowFromSystemServer(); 1629 } 1630 } 1631 } 1632 1633 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 1634 Rect anchorBounds, IAutofillWindowPresenter presenter) { 1635 final View anchor = findView(id); 1636 if (anchor == null) { 1637 return; 1638 } 1639 1640 AutofillCallback callback = null; 1641 synchronized (mLock) { 1642 if (mSessionId == sessionId) { 1643 AutofillClient client = getClient(); 1644 1645 if (client != null) { 1646 if (client.autofillClientRequestShowFillUi(anchor, width, height, 1647 anchorBounds, presenter) && mCallback != null) { 1648 callback = mCallback; 1649 } 1650 } 1651 } 1652 } 1653 1654 if (callback != null) { 1655 if (id.isVirtual()) { 1656 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1657 AutofillCallback.EVENT_INPUT_SHOWN); 1658 } else { 1659 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); 1660 } 1661 } 1662 } 1663 1664 private void authenticate(int sessionId, int authenticationId, IntentSender intent, 1665 Intent fillInIntent) { 1666 synchronized (mLock) { 1667 if (sessionId == mSessionId) { 1668 final AutofillClient client = getClient(); 1669 if (client != null) { 1670 // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() 1671 // before onAuthenticationResult() 1672 mOnInvisibleCalled = false; 1673 client.autofillClientAuthenticate(authenticationId, intent, fillInIntent); 1674 } 1675 } 1676 } 1677 } 1678 1679 private void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent) { 1680 final View anchor = findView(id); 1681 if (anchor == null) { 1682 return; 1683 } 1684 1685 AutofillCallback callback = null; 1686 synchronized (mLock) { 1687 if (mSessionId == sessionId) { 1688 AutofillClient client = getClient(); 1689 1690 if (client != null) { 1691 client.autofillClientDispatchUnhandledKey(anchor, keyEvent); 1692 } 1693 } 1694 } 1695 } 1696 1697 /** @hide */ 1698 public static final int SET_STATE_FLAG_ENABLED = 0x01; 1699 /** @hide */ 1700 public static final int SET_STATE_FLAG_RESET_SESSION = 0x02; 1701 /** @hide */ 1702 public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04; 1703 /** @hide */ 1704 public static final int SET_STATE_FLAG_DEBUG = 0x08; 1705 /** @hide */ 1706 public static final int SET_STATE_FLAG_VERBOSE = 0x10; 1707 1708 private void setState(int flags) { 1709 if (sVerbose) Log.v(TAG, "setState(" + flags + ")"); 1710 synchronized (mLock) { 1711 mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; 1712 if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { 1713 // Reset the session state 1714 resetSessionLocked(/* resetEnteredIds= */ true); 1715 } 1716 if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { 1717 // Reset connection to system 1718 mServiceClient = null; 1719 if (mServiceClientCleaner != null) { 1720 mServiceClientCleaner.clean(); 1721 mServiceClientCleaner = null; 1722 } 1723 } 1724 } 1725 sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; 1726 sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; 1727 } 1728 1729 /** 1730 * Sets a view as autofilled if the current value is the {code targetValue}. 1731 * 1732 * @param view The view that is to be autofilled 1733 * @param targetValue The value we want to fill into view 1734 */ 1735 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { 1736 AutofillValue currentValue = view.getAutofillValue(); 1737 if (Objects.equals(currentValue, targetValue)) { 1738 synchronized (mLock) { 1739 if (mLastAutofilledData == null) { 1740 mLastAutofilledData = new ParcelableMap(1); 1741 } 1742 mLastAutofilledData.put(getAutofillId(view), targetValue); 1743 } 1744 view.setAutofilled(true); 1745 } 1746 } 1747 1748 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 1749 synchronized (mLock) { 1750 if (sessionId != mSessionId) { 1751 return; 1752 } 1753 1754 final AutofillClient client = getClient(); 1755 if (client == null) { 1756 return; 1757 } 1758 1759 final int itemCount = ids.size(); 1760 int numApplied = 0; 1761 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; 1762 final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( 1763 Helper.toArray(ids)); 1764 1765 for (int i = 0; i < itemCount; i++) { 1766 final AutofillId id = ids.get(i); 1767 final AutofillValue value = values.get(i); 1768 final int viewId = id.getViewId(); 1769 final View view = views[i]; 1770 if (view == null) { 1771 Log.w(TAG, "autofill(): no View with id " + viewId); 1772 continue; 1773 } 1774 if (id.isVirtual()) { 1775 if (virtualValues == null) { 1776 // Most likely there will be just one view with virtual children. 1777 virtualValues = new ArrayMap<>(1); 1778 } 1779 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); 1780 if (valuesByParent == null) { 1781 // We don't know the size yet, but usually it will be just a few fields... 1782 valuesByParent = new SparseArray<>(5); 1783 virtualValues.put(view, valuesByParent); 1784 } 1785 valuesByParent.put(id.getVirtualChildId(), value); 1786 } else { 1787 // Mark the view as to be autofilled with 'value' 1788 if (mLastAutofilledData == null) { 1789 mLastAutofilledData = new ParcelableMap(itemCount - i); 1790 } 1791 mLastAutofilledData.put(id, value); 1792 1793 view.autofill(value); 1794 1795 // Set as autofilled if the values match now, e.g. when the value was updated 1796 // synchronously. 1797 // If autofill happens async, the view is set to autofilled in 1798 // notifyValueChanged. 1799 setAutofilledIfValuesIs(view, value); 1800 1801 numApplied++; 1802 } 1803 } 1804 1805 if (virtualValues != null) { 1806 for (int i = 0; i < virtualValues.size(); i++) { 1807 final View parent = virtualValues.keyAt(i); 1808 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); 1809 parent.autofill(childrenValues); 1810 numApplied += childrenValues.size(); 1811 } 1812 } 1813 1814 final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED) 1815 .setPackageName(mContext.getPackageName()) 1816 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) 1817 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); 1818 mMetricsLogger.write(log); 1819 } 1820 } 1821 1822 /** 1823 * Set the tracked views. 1824 * 1825 * @param trackedIds The views to be tracked. 1826 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. 1827 * @param saveOnFinish Finish the session once the activity is finished. 1828 * @param fillableIds Views that might anchor FillUI. 1829 * @param saveTriggerId View that when clicked triggers commit(). 1830 */ 1831 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, 1832 boolean saveOnAllViewsInvisible, boolean saveOnFinish, 1833 @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { 1834 synchronized (mLock) { 1835 if (mEnabled && mSessionId == sessionId) { 1836 if (saveOnAllViewsInvisible) { 1837 mTrackedViews = new TrackedViews(trackedIds); 1838 } else { 1839 mTrackedViews = null; 1840 } 1841 mSaveOnFinish = saveOnFinish; 1842 if (fillableIds != null) { 1843 if (mFillableIds == null) { 1844 mFillableIds = new ArraySet<>(fillableIds.length); 1845 } 1846 for (AutofillId id : fillableIds) { 1847 mFillableIds.add(id); 1848 } 1849 if (sVerbose) { 1850 Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds 1851 + ", mFillableIds" + mFillableIds); 1852 } 1853 } 1854 1855 if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { 1856 // Turn off trigger on previous view id. 1857 setNotifyOnClickLocked(mSaveTriggerId, false); 1858 } 1859 1860 if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { 1861 // Turn on trigger on new view id. 1862 mSaveTriggerId = saveTriggerId; 1863 setNotifyOnClickLocked(mSaveTriggerId, true); 1864 } 1865 } 1866 } 1867 } 1868 1869 private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { 1870 final View view = findView(id); 1871 if (view == null) { 1872 Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); 1873 return; 1874 } 1875 view.setNotifyAutofillManagerOnClick(notify); 1876 } 1877 1878 private void setSaveUiState(int sessionId, boolean shown) { 1879 if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); 1880 synchronized (mLock) { 1881 if (mSessionId != NO_SESSION) { 1882 // Race condition: app triggered a new session after the previous session was 1883 // finished but before server called setSaveUiState() - need to cancel the new 1884 // session to avoid further inconsistent behavior. 1885 Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown 1886 + ") called on existing session " + mSessionId + "; cancelling it"); 1887 cancelSessionLocked(); 1888 } 1889 if (shown) { 1890 mSessionId = sessionId; 1891 mState = STATE_SHOWING_SAVE_UI; 1892 } else { 1893 mSessionId = NO_SESSION; 1894 mState = STATE_UNKNOWN; 1895 } 1896 } 1897 } 1898 1899 /** 1900 * Marks the state of the session as finished. 1901 * 1902 * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} 1903 * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or 1904 * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill 1905 * requests for the activity). 1906 */ 1907 private void setSessionFinished(int newState) { 1908 synchronized (mLock) { 1909 if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState); 1910 resetSessionLocked(/* resetEnteredIds= */ false); 1911 mState = newState; 1912 } 1913 } 1914 1915 private void requestHideFillUi(AutofillId id) { 1916 final View anchor = findView(id); 1917 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor); 1918 if (anchor == null) { 1919 return; 1920 } 1921 requestHideFillUi(id, anchor); 1922 } 1923 1924 private void requestHideFillUi(AutofillId id, View anchor) { 1925 1926 AutofillCallback callback = null; 1927 synchronized (mLock) { 1928 // We do not check the session id for two reasons: 1929 // 1. If local and remote session id are off sync the UI would be stuck shown 1930 // 2. There is a race between the user state being destroyed due the fill 1931 // service being uninstalled and the UI being dismissed. 1932 AutofillClient client = getClient(); 1933 if (client != null) { 1934 if (client.autofillClientRequestHideFillUi() && mCallback != null) { 1935 callback = mCallback; 1936 } 1937 } 1938 } 1939 1940 if (callback != null) { 1941 if (id.isVirtual()) { 1942 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1943 AutofillCallback.EVENT_INPUT_HIDDEN); 1944 } else { 1945 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); 1946 } 1947 } 1948 } 1949 1950 private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 1951 if (sVerbose) { 1952 Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id 1953 + ", sessionFinishedState=" + sessionFinishedState); 1954 } 1955 final View anchor = findView(id); 1956 if (anchor == null) { 1957 return; 1958 } 1959 1960 AutofillCallback callback = null; 1961 synchronized (mLock) { 1962 if (mSessionId == sessionId && getClient() != null) { 1963 callback = mCallback; 1964 } 1965 } 1966 1967 if (callback != null) { 1968 if (id.isVirtual()) { 1969 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1970 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1971 } else { 1972 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1973 } 1974 } 1975 1976 if (sessionFinishedState != 0) { 1977 // Callback call was "hijacked" to also update the session state. 1978 setSessionFinished(sessionFinishedState); 1979 } 1980 } 1981 1982 /** 1983 * Find a single view by its id. 1984 * 1985 * @param autofillId The autofill id of the view 1986 * 1987 * @return The view or {@code null} if view was not found 1988 */ 1989 private View findView(@NonNull AutofillId autofillId) { 1990 final AutofillClient client = getClient(); 1991 if (client != null) { 1992 return client.autofillClientFindViewByAutofillIdTraversal(autofillId); 1993 } 1994 return null; 1995 } 1996 1997 /** @hide */ 1998 public boolean hasAutofillFeature() { 1999 return mService != null; 2000 } 2001 2002 /** @hide */ 2003 public void onPendingSaveUi(int operation, IBinder token) { 2004 if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 2005 2006 synchronized (mLock) { 2007 try { 2008 mService.onPendingSaveUi(operation, token); 2009 } catch (RemoteException e) { 2010 e.rethrowFromSystemServer(); 2011 } 2012 } 2013 } 2014 2015 /** @hide */ 2016 public void dump(String outerPrefix, PrintWriter pw) { 2017 pw.print(outerPrefix); pw.println("AutofillManager:"); 2018 final String pfx = outerPrefix + " "; 2019 pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); 2020 pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); 2021 pw.print(pfx); pw.print("context: "); pw.println(mContext); 2022 pw.print(pfx); pw.print("client: "); pw.println(getClient()); 2023 pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); 2024 pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); 2025 pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); 2026 pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); 2027 pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); 2028 pw.print(pfx); pw.print("tracked views: "); 2029 if (mTrackedViews == null) { 2030 pw.println("null"); 2031 } else { 2032 final String pfx2 = pfx + " "; 2033 pw.println(); 2034 pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds); 2035 pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); 2036 } 2037 pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); 2038 pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds); 2039 pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); 2040 pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); 2041 pw.print(pfx); pw.print("compat mode enabled: "); pw.println( 2042 isCompatibilityModeEnabledLocked()); 2043 pw.print(pfx); pw.print("debug: "); pw.print(sDebug); 2044 pw.print(" verbose: "); pw.println(sVerbose); 2045 } 2046 2047 @GuardedBy("mLock") 2048 private String getStateAsStringLocked() { 2049 switch (mState) { 2050 case STATE_UNKNOWN: 2051 return "STATE_UNKNOWN"; 2052 case STATE_ACTIVE: 2053 return "STATE_ACTIVE"; 2054 case STATE_FINISHED: 2055 return "STATE_FINISHED"; 2056 case STATE_SHOWING_SAVE_UI: 2057 return "STATE_SHOWING_SAVE_UI"; 2058 case STATE_DISABLED_BY_SERVICE: 2059 return "STATE_DISABLED_BY_SERVICE"; 2060 default: 2061 return "INVALID:" + mState; 2062 } 2063 } 2064 2065 @GuardedBy("mLock") 2066 private boolean isActiveLocked() { 2067 return mState == STATE_ACTIVE; 2068 } 2069 2070 @GuardedBy("mLock") 2071 private boolean isDisabledByServiceLocked() { 2072 return mState == STATE_DISABLED_BY_SERVICE; 2073 } 2074 2075 @GuardedBy("mLock") 2076 private boolean isFinishedLocked() { 2077 return mState == STATE_FINISHED; 2078 } 2079 2080 private void post(Runnable runnable) { 2081 final AutofillClient client = getClient(); 2082 if (client == null) { 2083 if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); 2084 return; 2085 } 2086 client.autofillClientRunOnUiThread(runnable); 2087 } 2088 2089 /** 2090 * Implementation of the accessibility based compatibility. 2091 */ 2092 private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy { 2093 @GuardedBy("mLock") 2094 private final Rect mFocusedBounds = new Rect(); 2095 @GuardedBy("mLock") 2096 private final Rect mTempBounds = new Rect(); 2097 2098 @GuardedBy("mLock") 2099 private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2100 @GuardedBy("mLock") 2101 private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2102 2103 // Need to report a fake service in case a11y clients check the service list 2104 @NonNull 2105 @GuardedBy("mLock") 2106 AccessibilityServiceInfo mCompatServiceInfo; 2107 2108 CompatibilityBridge() { 2109 final AccessibilityManager am = AccessibilityManager.getInstance(mContext); 2110 am.setAccessibilityPolicy(this); 2111 } 2112 2113 private AccessibilityServiceInfo getCompatServiceInfo() { 2114 synchronized (mLock) { 2115 if (mCompatServiceInfo != null) { 2116 return mCompatServiceInfo; 2117 } 2118 final Intent intent = new Intent(); 2119 intent.setComponent(new ComponentName("android", 2120 "com.android.server.autofill.AutofillCompatAccessibilityService")); 2121 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService( 2122 intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 2123 try { 2124 mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); 2125 } catch (XmlPullParserException | IOException e) { 2126 Log.e(TAG, "Cannot find compat autofill service:" + intent); 2127 throw new IllegalStateException("Cannot find compat autofill service"); 2128 } 2129 return mCompatServiceInfo; 2130 } 2131 } 2132 2133 @Override 2134 public boolean isEnabled(boolean accessibilityEnabled) { 2135 return true; 2136 } 2137 2138 @Override 2139 public int getRelevantEventTypes(int relevantEventTypes) { 2140 return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED 2141 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED 2142 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 2143 } 2144 2145 @Override 2146 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( 2147 List<AccessibilityServiceInfo> installedServices) { 2148 if (installedServices == null) { 2149 installedServices = new ArrayList<>(); 2150 } 2151 installedServices.add(getCompatServiceInfo()); 2152 return installedServices; 2153 } 2154 2155 @Override 2156 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 2157 int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) { 2158 if (enabledService == null) { 2159 enabledService = new ArrayList<>(); 2160 } 2161 enabledService.add(getCompatServiceInfo()); 2162 return enabledService; 2163 } 2164 2165 @Override 2166 public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event, 2167 boolean accessibilityEnabled, int relevantEventTypes) { 2168 switch (event.getEventType()) { 2169 case AccessibilityEvent.TYPE_VIEW_FOCUSED: { 2170 synchronized (mLock) { 2171 if (mFocusedWindowId == event.getWindowId() 2172 && mFocusedNodeId == event.getSourceNodeId()) { 2173 return event; 2174 } 2175 if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID 2176 && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) { 2177 notifyViewExited(mFocusedWindowId, mFocusedNodeId); 2178 mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2179 mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2180 mFocusedBounds.set(0, 0, 0, 0); 2181 } 2182 final int windowId = event.getWindowId(); 2183 final long nodeId = event.getSourceNodeId(); 2184 if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) { 2185 mFocusedWindowId = windowId; 2186 mFocusedNodeId = nodeId; 2187 } 2188 } 2189 } break; 2190 2191 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: { 2192 synchronized (mLock) { 2193 if (mFocusedWindowId == event.getWindowId() 2194 && mFocusedNodeId == event.getSourceNodeId()) { 2195 notifyValueChanged(event.getWindowId(), event.getSourceNodeId()); 2196 } 2197 } 2198 } break; 2199 2200 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { 2201 final AutofillClient client = getClient(); 2202 if (client != null) { 2203 synchronized (mLock) { 2204 if (client.autofillClientIsFillUiShowing()) { 2205 notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds); 2206 } 2207 updateTrackedViewsLocked(); 2208 } 2209 } 2210 } break; 2211 } 2212 2213 return accessibilityEnabled ? event : null; 2214 } 2215 2216 private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) { 2217 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2218 if (!isVirtualNode(virtualId)) { 2219 return false; 2220 } 2221 final View view = findViewByAccessibilityId(windowId, nodeId); 2222 if (view == null) { 2223 return false; 2224 } 2225 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2226 if (node == null) { 2227 return false; 2228 } 2229 if (!node.isEditable()) { 2230 return false; 2231 } 2232 final Rect newBounds = mTempBounds; 2233 node.getBoundsInScreen(newBounds); 2234 if (newBounds.equals(focusedBounds)) { 2235 return false; 2236 } 2237 focusedBounds.set(newBounds); 2238 AutofillManager.this.notifyViewEntered(view, virtualId, newBounds); 2239 return true; 2240 } 2241 2242 private void notifyViewExited(int windowId, long nodeId) { 2243 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2244 if (!isVirtualNode(virtualId)) { 2245 return; 2246 } 2247 final View view = findViewByAccessibilityId(windowId, nodeId); 2248 if (view == null) { 2249 return; 2250 } 2251 AutofillManager.this.notifyViewExited(view, virtualId); 2252 } 2253 2254 private void notifyValueChanged(int windowId, long nodeId) { 2255 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2256 if (!isVirtualNode(virtualId)) { 2257 return; 2258 } 2259 final View view = findViewByAccessibilityId(windowId, nodeId); 2260 if (view == null) { 2261 return; 2262 } 2263 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2264 if (node == null) { 2265 return; 2266 } 2267 AutofillManager.this.notifyValueChanged(view, virtualId, 2268 AutofillValue.forText(node.getText())); 2269 } 2270 2271 @GuardedBy("mLock") 2272 private void updateTrackedViewsLocked() { 2273 if (mTrackedViews != null) { 2274 mTrackedViews.onVisibleForAutofillChangedLocked(); 2275 } 2276 } 2277 2278 private View findViewByAccessibilityId(int windowId, long nodeId) { 2279 final AutofillClient client = getClient(); 2280 if (client == null) { 2281 return null; 2282 } 2283 final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId); 2284 return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId); 2285 } 2286 2287 private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) { 2288 final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 2289 if (provider == null) { 2290 return null; 2291 } 2292 return provider.createAccessibilityNodeInfo(virtualId); 2293 } 2294 2295 private boolean isVirtualNode(int nodeId) { 2296 return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID 2297 && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 2298 } 2299 } 2300 2301 /** 2302 * View tracking information. Once all tracked views become invisible the session is finished. 2303 */ 2304 private class TrackedViews { 2305 /** Visible tracked views */ 2306 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds; 2307 2308 /** Invisible tracked views */ 2309 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds; 2310 2311 /** 2312 * Check if set is null or value is in set. 2313 * 2314 * @param set The set or null (== empty set) 2315 * @param value The value that might be in the set 2316 * 2317 * @return {@code true} iff set is not empty and value is in set 2318 */ 2319 // TODO: move to Helper as static method 2320 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { 2321 return set != null && set.contains(value); 2322 } 2323 2324 /** 2325 * Add a value to a set. If set is null, create a new set. 2326 * 2327 * @param set The set or null (== empty set) 2328 * @param valueToAdd The value to add 2329 * 2330 * @return The set including the new value. If set was {@code null}, a set containing only 2331 * the new value. 2332 */ 2333 // TODO: move to Helper as static method 2334 @NonNull 2335 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { 2336 if (set == null) { 2337 set = new ArraySet<>(1); 2338 } 2339 2340 set.add(valueToAdd); 2341 2342 return set; 2343 } 2344 2345 /** 2346 * Remove a value from a set. 2347 * 2348 * @param set The set or null (== empty set) 2349 * @param valueToRemove The value to remove 2350 * 2351 * @return The set without the removed value. {@code null} if set was null, or is empty 2352 * after removal. 2353 */ 2354 // TODO: move to Helper as static method 2355 @Nullable 2356 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { 2357 if (set == null) { 2358 return null; 2359 } 2360 2361 set.remove(valueToRemove); 2362 2363 if (set.isEmpty()) { 2364 return null; 2365 } 2366 2367 return set; 2368 } 2369 2370 /** 2371 * Set the tracked views. 2372 * 2373 * @param trackedIds The views to be tracked 2374 */ 2375 TrackedViews(@Nullable AutofillId[] trackedIds) { 2376 final AutofillClient client = getClient(); 2377 if (!ArrayUtils.isEmpty(trackedIds) && client != null) { 2378 final boolean[] isVisible; 2379 2380 if (client.autofillClientIsVisibleForAutofill()) { 2381 if (sVerbose) Log.v(TAG, "client is visible, check tracked ids"); 2382 isVisible = client.autofillClientGetViewVisibility(trackedIds); 2383 } else { 2384 // All false 2385 isVisible = new boolean[trackedIds.length]; 2386 } 2387 2388 final int numIds = trackedIds.length; 2389 for (int i = 0; i < numIds; i++) { 2390 final AutofillId id = trackedIds[i]; 2391 2392 if (isVisible[i]) { 2393 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2394 } else { 2395 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2396 } 2397 } 2398 } 2399 2400 if (sVerbose) { 2401 Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): " 2402 + " mVisibleTrackedIds=" + mVisibleTrackedIds 2403 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); 2404 } 2405 2406 if (mVisibleTrackedIds == null) { 2407 finishSessionLocked(); 2408 } 2409 } 2410 2411 /** 2412 * Called when a {@link View view's} visibility changes. 2413 * 2414 * @param id the id of the view/virtual view whose visibility changed. 2415 * @param isVisible visible if the view is visible in the view hierarchy. 2416 */ 2417 @GuardedBy("mLock") 2418 void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { 2419 if (sDebug) { 2420 Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible=" 2421 + isVisible); 2422 } 2423 2424 if (isClientVisibleForAutofillLocked()) { 2425 if (isVisible) { 2426 if (isInSet(mInvisibleTrackedIds, id)) { 2427 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); 2428 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2429 } 2430 } else { 2431 if (isInSet(mVisibleTrackedIds, id)) { 2432 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); 2433 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2434 } 2435 } 2436 } 2437 2438 if (mVisibleTrackedIds == null) { 2439 if (sVerbose) { 2440 Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds); 2441 } 2442 finishSessionLocked(); 2443 } 2444 } 2445 2446 /** 2447 * Called once the client becomes visible. 2448 * 2449 * @see AutofillClient#autofillClientIsVisibleForAutofill() 2450 */ 2451 @GuardedBy("mLock") 2452 void onVisibleForAutofillChangedLocked() { 2453 // The visibility of the views might have changed while the client was not be visible, 2454 // hence update the visibility state for all views. 2455 AutofillClient client = getClient(); 2456 ArraySet<AutofillId> updatedVisibleTrackedIds = null; 2457 ArraySet<AutofillId> updatedInvisibleTrackedIds = null; 2458 if (client != null) { 2459 if (mInvisibleTrackedIds != null) { 2460 final ArrayList<AutofillId> orderedInvisibleIds = 2461 new ArrayList<>(mInvisibleTrackedIds); 2462 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2463 Helper.toArray(orderedInvisibleIds)); 2464 2465 final int numInvisibleTrackedIds = orderedInvisibleIds.size(); 2466 for (int i = 0; i < numInvisibleTrackedIds; i++) { 2467 final AutofillId id = orderedInvisibleIds.get(i); 2468 if (isVisible[i]) { 2469 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2470 2471 if (sDebug) { 2472 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); 2473 } 2474 } else { 2475 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2476 } 2477 } 2478 } 2479 2480 if (mVisibleTrackedIds != null) { 2481 final ArrayList<AutofillId> orderedVisibleIds = 2482 new ArrayList<>(mVisibleTrackedIds); 2483 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2484 Helper.toArray(orderedVisibleIds)); 2485 2486 final int numVisibleTrackedIds = orderedVisibleIds.size(); 2487 for (int i = 0; i < numVisibleTrackedIds; i++) { 2488 final AutofillId id = orderedVisibleIds.get(i); 2489 2490 if (isVisible[i]) { 2491 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2492 } else { 2493 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2494 2495 if (sDebug) { 2496 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); 2497 } 2498 } 2499 } 2500 } 2501 2502 mInvisibleTrackedIds = updatedInvisibleTrackedIds; 2503 mVisibleTrackedIds = updatedVisibleTrackedIds; 2504 } 2505 2506 if (mVisibleTrackedIds == null) { 2507 if (sVerbose) { 2508 Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); 2509 } 2510 finishSessionLocked(); 2511 } 2512 } 2513 } 2514 2515 /** 2516 * Callback for autofill related events. 2517 * 2518 * <p>Typically used for applications that display their own "auto-complete" views, so they can 2519 * enable / disable such views when the autofill UI is shown / hidden. 2520 */ 2521 public abstract static class AutofillCallback { 2522 2523 /** @hide */ 2524 @IntDef(prefix = { "EVENT_INPUT_" }, value = { 2525 EVENT_INPUT_SHOWN, 2526 EVENT_INPUT_HIDDEN, 2527 EVENT_INPUT_UNAVAILABLE 2528 }) 2529 @Retention(RetentionPolicy.SOURCE) 2530 public @interface AutofillEventType {} 2531 2532 /** 2533 * The autofill input UI associated with the view was shown. 2534 * 2535 * <p>If the view provides its own auto-complete UI and its currently shown, it 2536 * should be hidden upon receiving this event. 2537 */ 2538 public static final int EVENT_INPUT_SHOWN = 1; 2539 2540 /** 2541 * The autofill input UI associated with the view was hidden. 2542 * 2543 * <p>If the view provides its own auto-complete UI that was hidden upon a 2544 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. 2545 */ 2546 public static final int EVENT_INPUT_HIDDEN = 2; 2547 2548 /** 2549 * The autofill input UI associated with the view isn't shown because 2550 * autofill is not available. 2551 * 2552 * <p>If the view provides its own auto-complete UI but was not displaying it 2553 * to avoid flickering, it could shown it upon receiving this event. 2554 */ 2555 public static final int EVENT_INPUT_UNAVAILABLE = 3; 2556 2557 /** 2558 * Called after a change in the autofill state associated with a view. 2559 * 2560 * @param view view associated with the change. 2561 * 2562 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2563 */ 2564 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { 2565 } 2566 2567 /** 2568 * Called after a change in the autofill state associated with a virtual view. 2569 * 2570 * @param view parent view associated with the change. 2571 * @param virtualId id identifying the virtual child inside the parent view. 2572 * 2573 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2574 */ 2575 public void onAutofillEvent(@NonNull View view, int virtualId, 2576 @AutofillEventType int event) { 2577 } 2578 } 2579 2580 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { 2581 private final WeakReference<AutofillManager> mAfm; 2582 2583 AutofillManagerClient(AutofillManager autofillManager) { 2584 mAfm = new WeakReference<>(autofillManager); 2585 } 2586 2587 @Override 2588 public void setState(int flags) { 2589 final AutofillManager afm = mAfm.get(); 2590 if (afm != null) { 2591 afm.post(() -> afm.setState(flags)); 2592 } 2593 } 2594 2595 @Override 2596 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 2597 final AutofillManager afm = mAfm.get(); 2598 if (afm != null) { 2599 afm.post(() -> afm.autofill(sessionId, ids, values)); 2600 } 2601 } 2602 2603 @Override 2604 public void authenticate(int sessionId, int authenticationId, IntentSender intent, 2605 Intent fillInIntent) { 2606 final AutofillManager afm = mAfm.get(); 2607 if (afm != null) { 2608 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent)); 2609 } 2610 } 2611 2612 @Override 2613 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 2614 Rect anchorBounds, IAutofillWindowPresenter presenter) { 2615 final AutofillManager afm = mAfm.get(); 2616 if (afm != null) { 2617 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 2618 presenter)); 2619 } 2620 } 2621 2622 @Override 2623 public void requestHideFillUi(int sessionId, AutofillId id) { 2624 final AutofillManager afm = mAfm.get(); 2625 if (afm != null) { 2626 afm.post(() -> afm.requestHideFillUi(id)); 2627 } 2628 } 2629 2630 @Override 2631 public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 2632 final AutofillManager afm = mAfm.get(); 2633 if (afm != null) { 2634 afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); 2635 } 2636 } 2637 2638 @Override 2639 public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) { 2640 final AutofillManager afm = mAfm.get(); 2641 if (afm != null) { 2642 afm.post(() -> afm.dispatchUnhandledKey(sessionId, id, fullScreen)); 2643 } 2644 } 2645 2646 @Override 2647 public void startIntentSender(IntentSender intentSender, Intent intent) { 2648 final AutofillManager afm = mAfm.get(); 2649 if (afm != null) { 2650 afm.post(() -> { 2651 try { 2652 afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); 2653 } catch (IntentSender.SendIntentException e) { 2654 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); 2655 } 2656 }); 2657 } 2658 } 2659 2660 @Override 2661 public void setTrackedViews(int sessionId, AutofillId[] ids, 2662 boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, 2663 AutofillId saveTriggerId) { 2664 final AutofillManager afm = mAfm.get(); 2665 if (afm != null) { 2666 afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, 2667 saveOnFinish, fillableIds, saveTriggerId)); 2668 } 2669 } 2670 2671 @Override 2672 public void setSaveUiState(int sessionId, boolean shown) { 2673 final AutofillManager afm = mAfm.get(); 2674 if (afm != null) { 2675 afm.post(() -> afm.setSaveUiState(sessionId, shown)); 2676 } 2677 } 2678 2679 @Override 2680 public void setSessionFinished(int newState) { 2681 final AutofillManager afm = mAfm.get(); 2682 if (afm != null) { 2683 afm.post(() -> afm.setSessionFinished(newState)); 2684 } 2685 } 2686 } 2687} 2688