AutofillManager.java revision a9379d0b44ca1f68a0036d2b65218e17fa348514
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.view.autofill.Helper.sDebug; 20import static android.view.autofill.Helper.sVerbose; 21 22import android.annotation.IntDef; 23import android.annotation.NonNull; 24import android.annotation.Nullable; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentSender; 28import android.graphics.Rect; 29import android.metrics.LogMaker; 30import android.os.Bundle; 31import android.os.Parcelable; 32import android.os.RemoteException; 33import android.service.autofill.AutofillService; 34import android.service.autofill.FillEventHistory; 35import android.util.ArrayMap; 36import android.util.ArraySet; 37import android.util.Log; 38import android.util.SparseArray; 39import android.view.View; 40 41import com.android.internal.annotations.GuardedBy; 42import com.android.internal.logging.MetricsLogger; 43import com.android.internal.logging.nano.MetricsProto; 44 45import java.lang.annotation.Retention; 46import java.lang.annotation.RetentionPolicy; 47import java.lang.ref.WeakReference; 48import java.util.ArrayList; 49import java.util.List; 50import java.util.Objects; 51 52/** 53 * App entry point to the Autofill Framework. 54 * 55 * <p>It is safe to call into this from any thread. 56 */ 57public final class AutofillManager { 58 59 private static final String TAG = "AutofillManager"; 60 61 /** 62 * Intent extra: The assist structure which captures the filled screen. 63 * 64 * <p> 65 * Type: {@link android.app.assist.AssistStructure} 66 */ 67 public static final String EXTRA_ASSIST_STRUCTURE = 68 "android.view.autofill.extra.ASSIST_STRUCTURE"; 69 70 /** 71 * Intent extra: The result of an authentication operation. It is 72 * either a fully populated {@link android.service.autofill.FillResponse} 73 * or a fully populated {@link android.service.autofill.Dataset} if 74 * a response or a dataset is being authenticated respectively. 75 * 76 * <p> 77 * Type: {@link android.service.autofill.FillResponse} or a 78 * {@link android.service.autofill.Dataset} 79 */ 80 public static final String EXTRA_AUTHENTICATION_RESULT = 81 "android.view.autofill.extra.AUTHENTICATION_RESULT"; 82 83 /** 84 * Intent extra: The optional extras provided by the 85 * {@link android.service.autofill.AutofillService}. 86 * 87 * <p>For example, when the service responds to a {@link 88 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with 89 * a {@code FillResponse} that requires authentication, the Intent that launches the 90 * service authentication will contain the Bundle set by 91 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. 92 * 93 * <p> 94 * Type: {@link android.os.Bundle} 95 */ 96 public static final String EXTRA_CLIENT_STATE = 97 "android.view.autofill.extra.CLIENT_STATE"; 98 99 static final String SESSION_ID_TAG = "android:sessionId"; 100 static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; 101 102 /** 103 * @deprecated Use {@link android.service.autofill.FillRequest#FLAG_MANUAL_REQUEST} 104 * @hide 105 */ 106 // TODO(b/37563972): remove (and change value of private flags) 107 @Deprecated 108 public static final int FLAG_MANUAL_REQUEST = 0x1; 109 110 /** @hide */ public static final int ACTION_START_SESSION = 1; 111 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2; 112 /** @hide */ public static final int ACTION_VIEW_EXITED = 3; 113 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; 114 115 116 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; 117 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; 118 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; 119 120 /** Which bits in an authentication id are used for the dataset id */ 121 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF; 122 /** How many bits in an authentication id are used for the dataset id */ 123 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16; 124 /** @hide The index for an undefined data set */ 125 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF; 126 127 /** 128 * Makes an authentication id from a request id and a dataset id. 129 * 130 * @param requestId The request id. 131 * @param datasetId The dataset id. 132 * @return The authentication id. 133 * @hide 134 */ 135 public static int makeAuthenticationId(int requestId, int datasetId) { 136 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT) 137 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK); 138 } 139 140 /** 141 * Gets the request id from an authentication id. 142 * 143 * @param authRequestId The authentication id. 144 * @return The request id. 145 * @hide 146 */ 147 public static int getRequestIdFromAuthenticationId(int authRequestId) { 148 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT); 149 } 150 151 /** 152 * Gets the dataset id from an authentication id. 153 * 154 * @param authRequestId The authentication id. 155 * @return The dataset id. 156 * @hide 157 */ 158 public static int getDatasetIdFromAuthenticationId(int authRequestId) { 159 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK); 160 } 161 162 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 163 164 /** 165 * There is currently no session running. 166 * {@hide} 167 */ 168 public static final int NO_SESSION = Integer.MIN_VALUE; 169 170 private final IAutoFillManager mService; 171 172 private final Object mLock = new Object(); 173 174 @GuardedBy("mLock") 175 private IAutoFillManagerClient mServiceClient; 176 177 @GuardedBy("mLock") 178 private AutofillCallback mCallback; 179 180 private final Context mContext; 181 182 @GuardedBy("mLock") 183 private int mSessionId = NO_SESSION; 184 185 @GuardedBy("mLock") 186 private boolean mEnabled; 187 188 /** If a view changes to this mapping the autofill operation was successful */ 189 @GuardedBy("mLock") 190 @Nullable private ParcelableMap mLastAutofilledData; 191 192 /** If view tracking is enabled, contains the tracking state */ 193 @GuardedBy("mLock") 194 @Nullable private TrackedViews mTrackedViews; 195 196 /** @hide */ 197 public interface AutofillClient { 198 /** 199 * Asks the client to start an authentication flow. 200 * 201 * @param authenticationId A unique id of the authentication operation. 202 * @param intent The authentication intent. 203 * @param fillInIntent The authentication fill-in intent. 204 */ 205 void autofillCallbackAuthenticate(int authenticationId, IntentSender intent, 206 Intent fillInIntent); 207 208 /** 209 * Tells the client this manager has state to be reset. 210 */ 211 void autofillCallbackResetableStateAvailable(); 212 213 /** 214 * Request showing the autofill UI. 215 * 216 * @param anchor The real view the UI needs to anchor to. 217 * @param width The width of the fill UI content. 218 * @param height The height of the fill UI content. 219 * @param virtualBounds The bounds of the virtual decendant of the anchor. 220 * @param presenter The presenter that controls the fill UI window. 221 * @return Whether the UI was shown. 222 */ 223 boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height, 224 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter); 225 226 /** 227 * Request hiding the autofill UI. 228 * 229 * @return Whether the UI was hidden. 230 */ 231 boolean autofillCallbackRequestHideFillUi(); 232 233 /** 234 * Checks if views are currently attached and visible. 235 * 236 * @return And array with {@code true} iff the view is attached or visible 237 */ 238 @NonNull boolean[] getViewVisibility(@NonNull int[] viewId); 239 240 /** 241 * Checks is the client is currently visible as understood by autofill. 242 * 243 * @return {@code true} if the client is currently visible 244 */ 245 boolean isVisibleForAutofill(); 246 247 /** 248 * Find views by traversing the hierarchies of the client. 249 * 250 * @param viewIds The accessibility ids of the views to find 251 * 252 * @return And array containing the views, or {@code null} if not found 253 */ 254 @NonNull View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds); 255 } 256 257 /** 258 * @hide 259 */ 260 public AutofillManager(Context context, IAutoFillManager service) { 261 mContext = context; 262 mService = service; 263 } 264 265 /** 266 * Restore state after activity lifecycle 267 * 268 * @param savedInstanceState The state to be restored 269 * 270 * {@hide} 271 */ 272 public void onCreate(Bundle savedInstanceState) { 273 if (!hasAutofillFeature()) { 274 return; 275 } 276 synchronized (mLock) { 277 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); 278 279 if (mSessionId != NO_SESSION) { 280 Log.w(TAG, "New session was started before onCreate()"); 281 return; 282 } 283 284 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); 285 286 if (mSessionId != NO_SESSION) { 287 ensureServiceClientAddedIfNeededLocked(); 288 289 final AutofillClient client = getClientLocked(); 290 if (client != null) { 291 try { 292 final boolean sessionWasRestored = mService.restoreSession(mSessionId, 293 mContext.getActivityToken(), mServiceClient.asBinder()); 294 295 if (!sessionWasRestored) { 296 Log.w(TAG, "Session " + mSessionId + " could not be restored"); 297 mSessionId = NO_SESSION; 298 } else { 299 if (sDebug) { 300 Log.d(TAG, "session " + mSessionId + " was restored"); 301 } 302 303 client.autofillCallbackResetableStateAvailable(); 304 } 305 } catch (RemoteException e) { 306 Log.e(TAG, "Could not figure out if there was an autofill session", e); 307 } 308 } 309 } 310 } 311 } 312 313 /** 314 * Called once the client becomes visible. 315 * 316 * @see AutofillClient#isVisibleForAutofill() 317 * 318 * {@hide} 319 */ 320 public void onVisibleForAutofill() { 321 synchronized (mLock) { 322 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) { 323 mTrackedViews.onVisibleForAutofillLocked(); 324 } 325 } 326 } 327 328 /** 329 * Save state before activity lifecycle 330 * 331 * @param outState Place to store the state 332 * 333 * {@hide} 334 */ 335 public void onSaveInstanceState(Bundle outState) { 336 if (!hasAutofillFeature()) { 337 return; 338 } 339 synchronized (mLock) { 340 if (mSessionId != NO_SESSION) { 341 outState.putInt(SESSION_ID_TAG, mSessionId); 342 } 343 344 if (mLastAutofilledData != null) { 345 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); 346 } 347 } 348 } 349 350 /** 351 * Checks whether autofill is enabled for the current user. 352 * 353 * <p>Typically used to determine whether the option to explicitly request autofill should 354 * be offered - see {@link #requestAutofill(View)}. 355 * 356 * @return whether autofill is enabled for the current user. 357 */ 358 public boolean isEnabled() { 359 if (!hasAutofillFeature()) { 360 return false; 361 } 362 synchronized (mLock) { 363 ensureServiceClientAddedIfNeededLocked(); 364 return mEnabled; 365 } 366 } 367 368 /** 369 * Should always be called from {@link AutofillService#getFillEventHistory()}. 370 * 371 * @hide 372 */ 373 @Nullable public FillEventHistory getFillEventHistory() { 374 try { 375 return mService.getFillEventHistory(); 376 } catch (RemoteException e) { 377 e.rethrowFromSystemServer(); 378 return null; 379 } 380 } 381 382 /** 383 * Explicitly requests a new autofill context. 384 * 385 * <p>Normally, the autofill context is automatically started when autofillable views are 386 * focused, but this method should be used in the cases where it must be explicitly requested, 387 * like a view that provides a contextual menu allowing users to autofill the activity. 388 * 389 * @param view view requesting the new autofill context. 390 */ 391 public void requestAutofill(@NonNull View view) { 392 notifyViewEntered(view, FLAG_MANUAL_REQUEST); 393 } 394 395 /** 396 * Explicitly requests a new autofill context for virtual views. 397 * 398 * <p>Normally, the autofill context is automatically started when autofillable views are 399 * focused, but this method should be used in the cases where it must be explicitly requested, 400 * like a virtual view that provides a contextual menu allowing users to autofill the activity. 401 * 402 * @param view the {@link View} whose descendant is the virtual view. 403 * @param childId id identifying the virtual child inside the view. 404 * @param bounds child boundaries, relative to the top window. 405 */ 406 public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) { 407 notifyViewEntered(view, childId, bounds, FLAG_MANUAL_REQUEST); 408 } 409 410 /** 411 * Called when a {@link View} that supports autofill is entered. 412 * 413 * @param view {@link View} that was entered. 414 */ 415 public void notifyViewEntered(@NonNull View view) { 416 notifyViewEntered(view, 0); 417 } 418 419 private void notifyViewEntered(@NonNull View view, int flags) { 420 if (!hasAutofillFeature()) { 421 return; 422 } 423 AutofillCallback callback = null; 424 synchronized (mLock) { 425 ensureServiceClientAddedIfNeededLocked(); 426 427 if (!mEnabled) { 428 if (mCallback != null) { 429 callback = mCallback; 430 } 431 } else { 432 final AutofillId id = getAutofillId(view); 433 final AutofillValue value = view.getAutofillValue(); 434 435 if (mSessionId == NO_SESSION) { 436 // Starts new session. 437 startSessionLocked(id, null, value, flags); 438 } else { 439 // Update focus on existing session. 440 updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags); 441 } 442 } 443 } 444 445 if (callback != null) { 446 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 447 } 448 } 449 450 /** 451 * Called when a {@link View} that supports autofill is exited. 452 * 453 * @param view {@link View} that was exited. 454 */ 455 public void notifyViewExited(@NonNull View view) { 456 if (!hasAutofillFeature()) { 457 return; 458 } 459 synchronized (mLock) { 460 ensureServiceClientAddedIfNeededLocked(); 461 462 if (mEnabled && mSessionId != NO_SESSION) { 463 final AutofillId id = getAutofillId(view); 464 465 // Update focus on existing session. 466 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 467 } 468 } 469 } 470 471 /** 472 * Called when a {@link View view's} visibility changes. 473 * 474 * @param view {@link View} that was exited. 475 * @param isVisible visible if the view is visible in the view hierarchy. 476 * 477 * @hide 478 */ 479 public void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) { 480 synchronized (mLock) { 481 if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) { 482 mTrackedViews.notifyViewVisibilityChange(view, isVisible); 483 } 484 } 485 } 486 487 /** 488 * Called when a virtual view that supports autofill is entered. 489 * 490 * @param view the {@link View} whose descendant is the virtual view. 491 * @param childId id identifying the virtual child inside the view. 492 * @param bounds child boundaries, relative to the top window. 493 */ 494 public void notifyViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) { 495 notifyViewEntered(view, childId, bounds, 0); 496 } 497 498 private void notifyViewEntered(View view, int childId, Rect bounds, int flags) { 499 if (!hasAutofillFeature()) { 500 return; 501 } 502 AutofillCallback callback = null; 503 synchronized (mLock) { 504 ensureServiceClientAddedIfNeededLocked(); 505 506 if (!mEnabled) { 507 if (mCallback != null) { 508 callback = mCallback; 509 } 510 } else { 511 final AutofillId id = getAutofillId(view, childId); 512 513 if (mSessionId == NO_SESSION) { 514 // Starts new session. 515 startSessionLocked(id, bounds, null, flags); 516 } else { 517 // Update focus on existing session. 518 updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags); 519 } 520 } 521 } 522 523 if (callback != null) { 524 callback.onAutofillEvent(view, childId, 525 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 526 } 527 } 528 529 /** 530 * Called when a virtual view that supports autofill is exited. 531 * 532 * @param view the {@link View} whose descendant is the virtual view. 533 * @param childId id identifying the virtual child inside the view. 534 */ 535 public void notifyViewExited(@NonNull View view, int childId) { 536 if (!hasAutofillFeature()) { 537 return; 538 } 539 synchronized (mLock) { 540 ensureServiceClientAddedIfNeededLocked(); 541 542 if (mEnabled && mSessionId != NO_SESSION) { 543 final AutofillId id = getAutofillId(view, childId); 544 545 // Update focus on existing session. 546 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 547 } 548 } 549 } 550 551 /** 552 * Called to indicate the value of an autofillable {@link View} changed. 553 * 554 * @param view view whose value changed. 555 */ 556 public void notifyValueChanged(View view) { 557 if (!hasAutofillFeature()) { 558 return; 559 } 560 AutofillId id = null; 561 boolean valueWasRead = false; 562 AutofillValue value = null; 563 564 synchronized (mLock) { 565 // If the session is gone some fields might still be highlighted, hence we have to 566 // remove the isAutofilled property even if no sessions are active. 567 if (mLastAutofilledData == null) { 568 view.setAutofilled(false); 569 } else { 570 id = getAutofillId(view); 571 if (mLastAutofilledData.containsKey(id)) { 572 value = view.getAutofillValue(); 573 valueWasRead = true; 574 575 if (Objects.equals(mLastAutofilledData.get(id), value)) { 576 view.setAutofilled(true); 577 } else { 578 view.setAutofilled(false); 579 mLastAutofilledData.remove(id); 580 } 581 } else { 582 view.setAutofilled(false); 583 } 584 } 585 586 if (!mEnabled || mSessionId == NO_SESSION) { 587 return; 588 } 589 590 if (id == null) { 591 id = getAutofillId(view); 592 } 593 594 if (!valueWasRead) { 595 value = view.getAutofillValue(); 596 } 597 598 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 599 } 600 } 601 602 /** 603 * Called to indicate the value of an autofillable virtual {@link View} changed. 604 * 605 * @param view the {@link View} whose descendant is the virtual view. 606 * @param childId id identifying the virtual child inside the parent view. 607 * @param value new value of the child. 608 */ 609 public void notifyValueChanged(View view, int childId, AutofillValue value) { 610 if (!hasAutofillFeature()) { 611 return; 612 } 613 synchronized (mLock) { 614 if (!mEnabled || mSessionId == NO_SESSION) { 615 return; 616 } 617 618 final AutofillId id = getAutofillId(view, childId); 619 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 620 } 621 } 622 623 /** 624 * Called to indicate the current autofill context should be commited. 625 * 626 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should 627 * call this method after the form is submitted and another page is rendered. 628 */ 629 public void commit() { 630 if (!hasAutofillFeature()) { 631 return; 632 } 633 synchronized (mLock) { 634 if (!mEnabled && mSessionId == NO_SESSION) { 635 return; 636 } 637 638 finishSessionLocked(); 639 } 640 } 641 642 /** 643 * Called to indicate the current autofill context should be cancelled. 644 * 645 * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should 646 * call this method if the user does not post the form but moves to another form in this page. 647 */ 648 public void cancel() { 649 if (!hasAutofillFeature()) { 650 return; 651 } 652 synchronized (mLock) { 653 if (!mEnabled && mSessionId == NO_SESSION) { 654 return; 655 } 656 657 cancelSessionLocked(); 658 } 659 } 660 661 /** @hide */ 662 public void disableOwnedAutofillServices() { 663 disableAutofillServices(); 664 } 665 666 /** 667 * If the app calling this API has enabled autofill services they 668 * will be disabled. 669 */ 670 public void disableAutofillServices() { 671 if (!hasAutofillFeature()) { 672 return; 673 } 674 try { 675 mService.disableOwnedAutofillServices(mContext.getUserId()); 676 } catch (RemoteException e) { 677 throw e.rethrowFromSystemServer(); 678 } 679 } 680 681 /** 682 * Returns {@code true} if the calling application provides a {@link AutofillService} that is 683 * enabled for the current user, or {@code false} otherwise. 684 */ 685 public boolean hasEnabledAutofillServices() { 686 if (mService == null) return false; 687 688 try { 689 return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName()); 690 } catch (RemoteException e) { 691 throw e.rethrowFromSystemServer(); 692 } 693 } 694 695 /** 696 * Returns {@code true} if Autofill is supported for this user. 697 * 698 * <p>Autofill is typically supported, but it could be unsupported in cases like: 699 * <ol> 700 * <li>Low-end devices. 701 * <li>Device policy rules that forbid its usage. 702 * </ol> 703 */ 704 public boolean isAutofillSupported() { 705 if (mService == null) return false; 706 707 try { 708 return mService.isServiceSupported(mContext.getUserId()); 709 } catch (RemoteException e) { 710 throw e.rethrowFromSystemServer(); 711 } 712 } 713 714 private AutofillClient getClientLocked() { 715 if (mContext instanceof AutofillClient) { 716 return (AutofillClient) mContext; 717 } 718 return null; 719 } 720 721 /** @hide */ 722 public void onAuthenticationResult(int authenticationId, Intent data) { 723 if (!hasAutofillFeature()) { 724 return; 725 } 726 // TODO: the result code is being ignored, so this method is not reliably 727 // handling the cases where it's not RESULT_OK: it works fine if the service does not 728 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the 729 // service set the extra and returned RESULT_CANCELED... 730 731 if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); 732 733 synchronized (mLock) { 734 if (mSessionId == NO_SESSION || data == null) { 735 return; 736 } 737 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); 738 final Bundle responseData = new Bundle(); 739 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); 740 try { 741 mService.setAuthenticationResult(responseData, mSessionId, authenticationId, 742 mContext.getUserId()); 743 } catch (RemoteException e) { 744 Log.e(TAG, "Error delivering authentication result", e); 745 } 746 } 747 } 748 749 private static AutofillId getAutofillId(View view) { 750 return new AutofillId(view.getAccessibilityViewId()); 751 } 752 753 private static AutofillId getAutofillId(View parent, int childId) { 754 return new AutofillId(parent.getAccessibilityViewId(), childId); 755 } 756 757 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, 758 @NonNull AutofillValue value, int flags) { 759 if (sVerbose) { 760 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value 761 + ", flags=" + flags); 762 } 763 764 try { 765 mSessionId = mService.startSession(mContext.getActivityToken(), 766 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 767 mCallback != null, flags, mContext.getOpPackageName()); 768 final AutofillClient client = getClientLocked(); 769 if (client != null) { 770 client.autofillCallbackResetableStateAvailable(); 771 } 772 } catch (RemoteException e) { 773 throw e.rethrowFromSystemServer(); 774 } 775 } 776 777 private void finishSessionLocked() { 778 if (sVerbose) Log.v(TAG, "finishSessionLocked()"); 779 780 try { 781 mService.finishSession(mSessionId, mContext.getUserId()); 782 } catch (RemoteException e) { 783 throw e.rethrowFromSystemServer(); 784 } 785 786 mTrackedViews = null; 787 mSessionId = NO_SESSION; 788 } 789 790 private void cancelSessionLocked() { 791 if (sVerbose) Log.v(TAG, "cancelSessionLocked()"); 792 793 try { 794 mService.cancelSession(mSessionId, mContext.getUserId()); 795 } catch (RemoteException e) { 796 throw e.rethrowFromSystemServer(); 797 } 798 799 resetSessionLocked(); 800 } 801 802 private void resetSessionLocked() { 803 mSessionId = NO_SESSION; 804 mTrackedViews = null; 805 } 806 807 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, 808 int flags) { 809 if (sVerbose && action != ACTION_VIEW_EXITED) { 810 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds 811 + ", value=" + value + ", action=" + action + ", flags=" + flags); 812 } 813 814 try { 815 mService.updateSession(mSessionId, id, bounds, value, action, flags, 816 mContext.getUserId()); 817 } catch (RemoteException e) { 818 throw e.rethrowFromSystemServer(); 819 } 820 } 821 822 private void ensureServiceClientAddedIfNeededLocked() { 823 if (getClientLocked() == null) { 824 return; 825 } 826 827 if (mServiceClient == null) { 828 mServiceClient = new AutofillManagerClient(this); 829 try { 830 final int flags = mService.addClient(mServiceClient, mContext.getUserId()); 831 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; 832 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; 833 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; 834 } catch (RemoteException e) { 835 throw e.rethrowFromSystemServer(); 836 } 837 } 838 } 839 840 /** 841 * Registers a {@link AutofillCallback} to receive autofill events. 842 * 843 * @param callback callback to receive events. 844 */ 845 public void registerCallback(@Nullable AutofillCallback callback) { 846 if (!hasAutofillFeature()) { 847 return; 848 } 849 synchronized (mLock) { 850 if (callback == null) return; 851 852 final boolean hadCallback = mCallback != null; 853 mCallback = callback; 854 855 if (!hadCallback) { 856 try { 857 mService.setHasCallback(mSessionId, mContext.getUserId(), true); 858 } catch (RemoteException e) { 859 throw e.rethrowFromSystemServer(); 860 } 861 } 862 } 863 } 864 865 /** 866 * Unregisters a {@link AutofillCallback} to receive autofill events. 867 * 868 * @param callback callback to stop receiving events. 869 */ 870 public void unregisterCallback(@Nullable AutofillCallback callback) { 871 if (!hasAutofillFeature()) { 872 return; 873 } 874 synchronized (mLock) { 875 if (callback == null || mCallback == null || callback != mCallback) return; 876 877 mCallback = null; 878 879 try { 880 mService.setHasCallback(mSessionId, mContext.getUserId(), false); 881 } catch (RemoteException e) { 882 throw e.rethrowFromSystemServer(); 883 } 884 } 885 } 886 887 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 888 Rect anchorBounds, IAutofillWindowPresenter presenter) { 889 final View anchor = findView(id); 890 if (anchor == null) { 891 return; 892 } 893 894 AutofillCallback callback = null; 895 synchronized (mLock) { 896 if (mSessionId == sessionId) { 897 AutofillClient client = getClientLocked(); 898 899 if (client != null) { 900 if (client.autofillCallbackRequestShowFillUi(anchor, width, height, 901 anchorBounds, presenter) && mCallback != null) { 902 callback = mCallback; 903 } 904 } 905 } 906 } 907 908 if (callback != null) { 909 if (id.isVirtual()) { 910 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 911 AutofillCallback.EVENT_INPUT_SHOWN); 912 } else { 913 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); 914 } 915 } 916 } 917 918 private void authenticate(int sessionId, int authenticationId, IntentSender intent, 919 Intent fillInIntent) { 920 synchronized (mLock) { 921 if (sessionId == mSessionId) { 922 AutofillClient client = getClientLocked(); 923 if (client != null) { 924 client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent); 925 } 926 } 927 } 928 } 929 930 private void setState(boolean enabled, boolean resetSession, boolean resetClient) { 931 synchronized (mLock) { 932 mEnabled = enabled; 933 if (!mEnabled || resetSession) { 934 // Reset the session state 935 resetSessionLocked(); 936 } 937 if (resetClient) { 938 // Reset connection to system 939 mServiceClient = null; 940 } 941 } 942 } 943 944 /** 945 * Sets a view as autofilled if the current value is the {code targetValue}. 946 * 947 * @param view The view that is to be autofilled 948 * @param targetValue The value we want to fill into view 949 */ 950 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { 951 AutofillValue currentValue = view.getAutofillValue(); 952 if (Objects.equals(currentValue, targetValue)) { 953 synchronized (mLock) { 954 if (mLastAutofilledData == null) { 955 mLastAutofilledData = new ParcelableMap(1); 956 } 957 mLastAutofilledData.put(getAutofillId(view), targetValue); 958 } 959 view.setAutofilled(true); 960 } 961 } 962 963 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 964 synchronized (mLock) { 965 if (sessionId != mSessionId) { 966 return; 967 } 968 969 final AutofillClient client = getClientLocked(); 970 if (client == null) { 971 return; 972 } 973 974 final int itemCount = ids.size(); 975 int numApplied = 0; 976 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; 977 final View[] views = client.findViewsByAccessibilityIdTraversal(getViewIds(ids)); 978 979 for (int i = 0; i < itemCount; i++) { 980 final AutofillId id = ids.get(i); 981 final AutofillValue value = values.get(i); 982 final int viewId = id.getViewId(); 983 final View view = views[i]; 984 if (view == null) { 985 Log.w(TAG, "autofill(): no View with id " + viewId); 986 continue; 987 } 988 if (id.isVirtual()) { 989 if (virtualValues == null) { 990 // Most likely there will be just one view with virtual children. 991 virtualValues = new ArrayMap<>(1); 992 } 993 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); 994 if (valuesByParent == null) { 995 // We don't know the size yet, but usually it will be just a few fields... 996 valuesByParent = new SparseArray<>(5); 997 virtualValues.put(view, valuesByParent); 998 } 999 valuesByParent.put(id.getVirtualChildId(), value); 1000 } else { 1001 // Mark the view as to be autofilled with 'value' 1002 if (mLastAutofilledData == null) { 1003 mLastAutofilledData = new ParcelableMap(itemCount - i); 1004 } 1005 mLastAutofilledData.put(id, value); 1006 1007 view.autofill(value); 1008 1009 // Set as autofilled if the values match now, e.g. when the value was updated 1010 // synchronously. 1011 // If autofill happens async, the view is set to autofilled in 1012 // notifyValueChanged. 1013 setAutofilledIfValuesIs(view, value); 1014 1015 numApplied++; 1016 } 1017 } 1018 1019 if (virtualValues != null) { 1020 for (int i = 0; i < virtualValues.size(); i++) { 1021 final View parent = virtualValues.keyAt(i); 1022 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); 1023 parent.autofill(childrenValues); 1024 numApplied += childrenValues.size(); 1025 } 1026 } 1027 1028 final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED); 1029 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount); 1030 log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, 1031 numApplied); 1032 mMetricsLogger.write(log); 1033 } 1034 } 1035 1036 /** 1037 * Set the tracked views. 1038 * 1039 * @param trackedIds The views to be tracked 1040 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. 1041 */ 1042 private void setTrackedViews(int sessionId, List<AutofillId> trackedIds, 1043 boolean saveOnAllViewsInvisible) { 1044 synchronized (mLock) { 1045 if (mEnabled && mSessionId == sessionId) { 1046 if (saveOnAllViewsInvisible) { 1047 mTrackedViews = new TrackedViews(trackedIds); 1048 } else { 1049 mTrackedViews = null; 1050 } 1051 } 1052 } 1053 } 1054 1055 private void requestHideFillUi(int sessionId, AutofillId id) { 1056 final View anchor = findView(id); 1057 if (anchor == null) { 1058 return; 1059 } 1060 1061 AutofillCallback callback = null; 1062 synchronized (mLock) { 1063 // We do not check the session id for two reasons: 1064 // 1. If local and remote session id are off sync the UI would be stuck shown 1065 // 2. There is a race between the user state being destroyed due the fill 1066 // service being uninstalled and the UI being dismissed. 1067 AutofillClient client = getClientLocked(); 1068 if (client != null) { 1069 if (client.autofillCallbackRequestHideFillUi() && mCallback != null) { 1070 callback = mCallback; 1071 } 1072 } 1073 } 1074 1075 if (callback != null) { 1076 if (id.isVirtual()) { 1077 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1078 AutofillCallback.EVENT_INPUT_HIDDEN); 1079 } else { 1080 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); 1081 } 1082 } 1083 } 1084 1085 private void notifyNoFillUi(int sessionId, AutofillId id) { 1086 final View anchor = findView(id); 1087 if (anchor == null) { 1088 return; 1089 } 1090 1091 AutofillCallback callback = null; 1092 synchronized (mLock) { 1093 if (mSessionId == sessionId && getClientLocked() != null) { 1094 callback = mCallback; 1095 } 1096 } 1097 1098 if (callback != null) { 1099 if (id.isVirtual()) { 1100 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1101 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1102 } else { 1103 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1104 } 1105 1106 } 1107 } 1108 1109 /** 1110 * Get an array of viewIds from a List of {@link AutofillId}. 1111 * 1112 * @param autofillIds The autofill ids to convert 1113 * 1114 * @return The array of viewIds. 1115 */ 1116 @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) { 1117 final int numIds = autofillIds.size(); 1118 final int[] viewIds = new int[numIds]; 1119 for (int i = 0; i < numIds; i++) { 1120 viewIds[i] = autofillIds.get(i).getViewId(); 1121 } 1122 1123 return viewIds; 1124 } 1125 1126 /** 1127 * Find a single view by its id. 1128 * 1129 * @param autofillId The autofill id of the view 1130 * 1131 * @return The view or {@code null} if view was not found 1132 */ 1133 private View findView(@NonNull AutofillId autofillId) { 1134 final AutofillClient client = getClientLocked(); 1135 1136 if (client == null) { 1137 return null; 1138 } 1139 1140 return client.findViewsByAccessibilityIdTraversal(new int[]{autofillId.getViewId()})[0]; 1141 } 1142 1143 /** @hide */ 1144 public boolean hasAutofillFeature() { 1145 return mService != null; 1146 } 1147 1148 /** 1149 * View tracking information. Once all tracked views become invisible the session is finished. 1150 */ 1151 private class TrackedViews { 1152 /** Visible tracked views */ 1153 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds; 1154 1155 /** Invisible tracked views */ 1156 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds; 1157 1158 /** 1159 * Check if set is null or value is in set. 1160 * 1161 * @param set The set or null (== empty set) 1162 * @param value The value that might be in the set 1163 * 1164 * @return {@code true} iff set is not empty and value is in set 1165 */ 1166 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { 1167 return set != null && set.contains(value); 1168 } 1169 1170 /** 1171 * Add a value to a set. If set is null, create a new set. 1172 * 1173 * @param set The set or null (== empty set) 1174 * @param valueToAdd The value to add 1175 * 1176 * @return The set including the new value. If set was {@code null}, a set containing only 1177 * the new value. 1178 */ 1179 @NonNull 1180 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { 1181 if (set == null) { 1182 set = new ArraySet<>(1); 1183 } 1184 1185 set.add(valueToAdd); 1186 1187 return set; 1188 } 1189 1190 /** 1191 * Remove a value from a set. 1192 * 1193 * @param set The set or null (== empty set) 1194 * @param valueToRemove The value to remove 1195 * 1196 * @return The set without the removed value. {@code null} if set was null, or is empty 1197 * after removal. 1198 */ 1199 @Nullable 1200 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { 1201 if (set == null) { 1202 return null; 1203 } 1204 1205 set.remove(valueToRemove); 1206 1207 if (set.isEmpty()) { 1208 return null; 1209 } 1210 1211 return set; 1212 } 1213 1214 /** 1215 * Set the tracked views. 1216 * 1217 * @param trackedIds The views to be tracked 1218 */ 1219 TrackedViews(List<AutofillId> trackedIds) { 1220 mVisibleTrackedIds = null; 1221 mInvisibleTrackedIds = null; 1222 1223 AutofillClient client = getClientLocked(); 1224 if (trackedIds != null && client != null) { 1225 final boolean[] isVisible; 1226 1227 if (client.isVisibleForAutofill()) { 1228 isVisible = client.getViewVisibility(getViewIds(trackedIds)); 1229 } else { 1230 // All false 1231 isVisible = new boolean[trackedIds.size()]; 1232 } 1233 1234 final int numIds = trackedIds.size(); 1235 for (int i = 0; i < numIds; i++) { 1236 final AutofillId id = trackedIds.get(i); 1237 1238 if (isVisible[i]) { 1239 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 1240 } else { 1241 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 1242 } 1243 } 1244 } 1245 1246 if (sVerbose) { 1247 Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): " 1248 + " mVisibleTrackedIds=" + mVisibleTrackedIds 1249 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); 1250 } 1251 1252 if (mVisibleTrackedIds == null) { 1253 finishSessionLocked(); 1254 } 1255 } 1256 1257 /** 1258 * Called when a {@link View view's} visibility changes. 1259 * 1260 * @param view {@link View} that was exited. 1261 * @param isVisible visible if the view is visible in the view hierarchy. 1262 */ 1263 void notifyViewVisibilityChange(@NonNull View view, boolean isVisible) { 1264 AutofillId id = getAutofillId(view); 1265 AutofillClient client = getClientLocked(); 1266 1267 if (sDebug) { 1268 Log.d(TAG, "notifyViewVisibilityChange(): id=" + id + " isVisible=" 1269 + isVisible); 1270 } 1271 1272 if (client != null && client.isVisibleForAutofill()) { 1273 if (isVisible) { 1274 if (isInSet(mInvisibleTrackedIds, id)) { 1275 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); 1276 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 1277 } 1278 } else { 1279 if (isInSet(mVisibleTrackedIds, id)) { 1280 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); 1281 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 1282 } 1283 } 1284 } 1285 1286 if (mVisibleTrackedIds == null) { 1287 finishSessionLocked(); 1288 } 1289 } 1290 1291 /** 1292 * Called once the client becomes visible. 1293 * 1294 * @see AutofillClient#isVisibleForAutofill() 1295 */ 1296 void onVisibleForAutofillLocked() { 1297 // The visibility of the views might have changed while the client was not be visible, 1298 // hence update the visibility state for all views. 1299 AutofillClient client = getClientLocked(); 1300 ArraySet<AutofillId> updatedVisibleTrackedIds = null; 1301 ArraySet<AutofillId> updatedInvisibleTrackedIds = null; 1302 if (client != null) { 1303 if (mInvisibleTrackedIds != null) { 1304 final ArrayList<AutofillId> orderedInvisibleIds = 1305 new ArrayList<>(mInvisibleTrackedIds); 1306 final boolean[] isVisible = client.getViewVisibility( 1307 getViewIds(orderedInvisibleIds)); 1308 1309 final int numInvisibleTrackedIds = orderedInvisibleIds.size(); 1310 for (int i = 0; i < numInvisibleTrackedIds; i++) { 1311 final AutofillId id = orderedInvisibleIds.get(i); 1312 if (isVisible[i]) { 1313 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 1314 1315 if (sDebug) { 1316 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); 1317 } 1318 } else { 1319 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 1320 } 1321 } 1322 } 1323 1324 if (mVisibleTrackedIds != null) { 1325 final ArrayList<AutofillId> orderedVisibleIds = 1326 new ArrayList<>(mVisibleTrackedIds); 1327 final boolean[] isVisible = client.getViewVisibility( 1328 getViewIds(orderedVisibleIds)); 1329 1330 final int numVisibleTrackedIds = orderedVisibleIds.size(); 1331 for (int i = 0; i < numVisibleTrackedIds; i++) { 1332 final AutofillId id = orderedVisibleIds.get(i); 1333 1334 if (isVisible[i]) { 1335 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 1336 } else { 1337 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 1338 1339 if (sDebug) { 1340 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); 1341 } 1342 } 1343 } 1344 } 1345 1346 mInvisibleTrackedIds = updatedInvisibleTrackedIds; 1347 mVisibleTrackedIds = updatedVisibleTrackedIds; 1348 } 1349 1350 if (mVisibleTrackedIds == null) { 1351 finishSessionLocked(); 1352 } 1353 } 1354 } 1355 1356 /** 1357 * Callback for auto-fill related events. 1358 * 1359 * <p>Typically used for applications that display their own "auto-complete" views, so they can 1360 * enable / disable such views when the auto-fill UI affordance is shown / hidden. 1361 */ 1362 public abstract static class AutofillCallback { 1363 1364 /** @hide */ 1365 @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN}) 1366 @Retention(RetentionPolicy.SOURCE) 1367 public @interface AutofillEventType {} 1368 1369 /** 1370 * The auto-fill input UI affordance associated with the view was shown. 1371 * 1372 * <p>If the view provides its own auto-complete UI affordance and its currently shown, it 1373 * should be hidden upon receiving this event. 1374 */ 1375 public static final int EVENT_INPUT_SHOWN = 1; 1376 1377 /** 1378 * The auto-fill input UI affordance associated with the view was hidden. 1379 * 1380 * <p>If the view provides its own auto-complete UI affordance that was hidden upon a 1381 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. 1382 */ 1383 public static final int EVENT_INPUT_HIDDEN = 2; 1384 1385 /** 1386 * The auto-fill input UI affordance associated with the view won't be shown because 1387 * autofill is not available. 1388 * 1389 * <p>If the view provides its own auto-complete UI affordance but was not displaying it 1390 * to avoid flickering, it could shown it upon receiving this event. 1391 */ 1392 public static final int EVENT_INPUT_UNAVAILABLE = 3; 1393 1394 /** 1395 * Called after a change in the autofill state associated with a view. 1396 * 1397 * @param view view associated with the change. 1398 * 1399 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 1400 */ 1401 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { 1402 } 1403 1404 /** 1405 * Called after a change in the autofill state associated with a virtual view. 1406 * 1407 * @param view parent view associated with the change. 1408 * @param childId id identifying the virtual child inside the parent view. 1409 * 1410 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 1411 */ 1412 public void onAutofillEvent(@NonNull View view, int childId, @AutofillEventType int event) { 1413 } 1414 } 1415 1416 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { 1417 private final WeakReference<AutofillManager> mAfm; 1418 1419 AutofillManagerClient(AutofillManager autofillManager) { 1420 mAfm = new WeakReference<>(autofillManager); 1421 } 1422 1423 @Override 1424 public void setState(boolean enabled, boolean resetSession, boolean resetClient) { 1425 final AutofillManager afm = mAfm.get(); 1426 if (afm != null) { 1427 afm.mContext.getMainThreadHandler().post( 1428 () -> afm.setState(enabled, resetSession, resetClient)); 1429 } 1430 } 1431 1432 @Override 1433 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 1434 final AutofillManager afm = mAfm.get(); 1435 if (afm != null) { 1436 afm.mContext.getMainThreadHandler().post( 1437 () -> afm.autofill(sessionId, ids, values)); 1438 } 1439 } 1440 1441 @Override 1442 public void authenticate(int sessionId, int authenticationId, IntentSender intent, 1443 Intent fillInIntent) { 1444 final AutofillManager afm = mAfm.get(); 1445 if (afm != null) { 1446 afm.mContext.getMainThreadHandler().post( 1447 () -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent)); 1448 } 1449 } 1450 1451 @Override 1452 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 1453 Rect anchorBounds, IAutofillWindowPresenter presenter) { 1454 final AutofillManager afm = mAfm.get(); 1455 if (afm != null) { 1456 afm.mContext.getMainThreadHandler().post( 1457 () -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 1458 presenter)); 1459 } 1460 } 1461 1462 @Override 1463 public void requestHideFillUi(int sessionId, AutofillId id) { 1464 final AutofillManager afm = mAfm.get(); 1465 if (afm != null) { 1466 afm.mContext.getMainThreadHandler().post( 1467 () -> afm.requestHideFillUi(sessionId, id)); 1468 } 1469 } 1470 1471 @Override 1472 public void notifyNoFillUi(int sessionId, AutofillId id) { 1473 final AutofillManager afm = mAfm.get(); 1474 if (afm != null) { 1475 afm.mContext.getMainThreadHandler().post(() -> afm.notifyNoFillUi(sessionId, id)); 1476 } 1477 } 1478 1479 @Override 1480 public void startIntentSender(IntentSender intentSender) { 1481 final AutofillManager afm = mAfm.get(); 1482 if (afm != null) { 1483 afm.mContext.getMainThreadHandler().post(() -> { 1484 try { 1485 afm.mContext.startIntentSender(intentSender, null, 0, 0, 0); 1486 } catch (IntentSender.SendIntentException e) { 1487 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); 1488 } 1489 }); 1490 } 1491 } 1492 1493 @Override 1494 public void setTrackedViews(int sessionId, List<AutofillId> ids, 1495 boolean saveOnAllViewsInvisible) { 1496 final AutofillManager afm = mAfm.get(); 1497 if (afm != null) { 1498 afm.mContext.getMainThreadHandler().post( 1499 () -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible) 1500 ); 1501 } 1502 } 1503 } 1504} 1505