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