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