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