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