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