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