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