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