AutofillManager.java revision 9a15c0f5a33d27e8554055b57d865f1c3af97b4d
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.accessibilityservice.AccessibilityServiceInfo; 24import android.annotation.IntDef; 25import android.annotation.NonNull; 26import android.annotation.Nullable; 27import android.annotation.SystemService; 28import android.content.ComponentName; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentSender; 32import android.content.pm.PackageManager; 33import android.content.pm.ResolveInfo; 34import android.graphics.Rect; 35import android.metrics.LogMaker; 36import android.os.Bundle; 37import android.os.IBinder; 38import android.os.Parcelable; 39import android.os.RemoteException; 40import android.service.autofill.AutofillService; 41import android.service.autofill.FillEventHistory; 42import android.service.autofill.UserData; 43import android.util.ArrayMap; 44import android.util.ArraySet; 45import android.util.Log; 46import android.util.SparseArray; 47import android.view.Choreographer; 48import android.view.View; 49import android.view.accessibility.AccessibilityEvent; 50import android.view.accessibility.AccessibilityManager; 51import android.view.accessibility.AccessibilityNodeInfo; 52import android.view.accessibility.AccessibilityNodeProvider; 53import android.view.accessibility.AccessibilityWindowInfo; 54 55import com.android.internal.annotations.GuardedBy; 56import com.android.internal.logging.MetricsLogger; 57import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 58import com.android.internal.util.ArrayUtils; 59import com.android.internal.util.Preconditions; 60 61import org.xmlpull.v1.XmlPullParserException; 62 63import java.io.IOException; 64import java.io.PrintWriter; 65import java.lang.annotation.Retention; 66import java.lang.annotation.RetentionPolicy; 67import java.lang.ref.WeakReference; 68import java.util.ArrayList; 69import java.util.Arrays; 70import java.util.Collections; 71import java.util.List; 72import java.util.Objects; 73 74import sun.misc.Cleaner; 75 76// TODO: use java.lang.ref.Cleaner once Android supports Java 9 77 78/** 79 * The {@link AutofillManager} provides ways for apps and custom views to integrate with the 80 * Autofill Framework lifecycle. 81 * 82 * <p>The autofill lifecycle starts with the creation of an autofill context associated with an 83 * activity context; the autofill context is created when one of the following methods is called for 84 * the first time in an activity context, and the current user has an enabled autofill service: 85 * 86 * <ul> 87 * <li>{@link #notifyViewEntered(View)} 88 * <li>{@link #notifyViewEntered(View, int, Rect)} 89 * <li>{@link #requestAutofill(View)} 90 * </ul> 91 * 92 * <p>Tipically, the context is automatically created when the first view of the activity is 93 * focused because {@code View.onFocusChanged()} indirectly calls 94 * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to 95 * explicitly create it (for example, a custom view developer could offer a contextual menu action 96 * in a text-field view to let users manually request autofill). 97 * 98 * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure} 99 * that represents the view hierarchy by calling 100 * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views 101 * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in 102 * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and 103 * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in 104 * the hierarchy. 105 * 106 * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which 107 * parses it looking for views that can be autofilled. If the service finds such views, it returns 108 * a data structure to the Android System containing the following optional info: 109 * 110 * <ul> 111 * <li>Datasets used to autofill subsets of views in the activity. 112 * <li>Id of views that the service can save their values for future autofilling. 113 * </ul> 114 * 115 * <p>When the service returns datasets, the Android System displays an autofill dataset picker 116 * UI associated with the view, when the view is focused on and is part of a dataset. 117 * The application can be notified when the UI is shown by registering an 118 * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user 119 * selects a dataset from the UI, all views present in the dataset are autofilled, through 120 * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. 121 * 122 * <p>When the service returns ids of savable views, the Android System keeps track of changes 123 * made to these views, so they can be used to determine if the autofill save UI is shown later. 124 * 125 * <p>The context is then finished when one of the following occurs: 126 * 127 * <ul> 128 * <li>{@link #commit()} is called or all savable views are gone. 129 * <li>{@link #cancel()} is called. 130 * </ul> 131 * 132 * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System 133 * shows an autofill save UI if the value of savable views have changed. If the user selects the 134 * option to Save, the current value of the views is then sent to the autofill service. 135 * 136 * <p>It is safe to call into its methods from any thread. 137 */ 138@SystemService(Context.AUTOFILL_MANAGER_SERVICE) 139public final class AutofillManager { 140 141 private static final String TAG = "AutofillManager"; 142 143 /** 144 * Intent extra: The assist structure which captures the filled screen. 145 * 146 * <p> 147 * Type: {@link android.app.assist.AssistStructure} 148 */ 149 public static final String EXTRA_ASSIST_STRUCTURE = 150 "android.view.autofill.extra.ASSIST_STRUCTURE"; 151 152 /** 153 * Intent extra: The result of an authentication operation. It is 154 * either a fully populated {@link android.service.autofill.FillResponse} 155 * or a fully populated {@link android.service.autofill.Dataset} if 156 * a response or a dataset is being authenticated respectively. 157 * 158 * <p> 159 * Type: {@link android.service.autofill.FillResponse} or a 160 * {@link android.service.autofill.Dataset} 161 */ 162 public static final String EXTRA_AUTHENTICATION_RESULT = 163 "android.view.autofill.extra.AUTHENTICATION_RESULT"; 164 165 /** 166 * Intent extra: The optional extras provided by the 167 * {@link android.service.autofill.AutofillService}. 168 * 169 * <p>For example, when the service responds to a {@link 170 * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with 171 * a {@code FillResponse} that requires authentication, the Intent that launches the 172 * service authentication will contain the Bundle set by 173 * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. 174 * 175 * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service 176 * can also add this bundle to the {@link Intent} set as the 177 * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, 178 * so the bundle can be recovered later on 179 * {@link android.service.autofill.SaveRequest#getClientState()}. 180 * 181 * <p> 182 * Type: {@link android.os.Bundle} 183 */ 184 public static final String EXTRA_CLIENT_STATE = 185 "android.view.autofill.extra.CLIENT_STATE"; 186 187 /** @hide */ 188 public static final String EXTRA_RESTORE_SESSION_TOKEN = 189 "android.view.autofill.extra.RESTORE_SESSION_TOKEN"; 190 191 private static final String SESSION_ID_TAG = "android:sessionId"; 192 private static final String STATE_TAG = "android:state"; 193 private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; 194 195 /** @hide */ public static final int ACTION_START_SESSION = 1; 196 /** @hide */ public static final int ACTION_VIEW_ENTERED = 2; 197 /** @hide */ public static final int ACTION_VIEW_EXITED = 3; 198 /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; 199 200 201 /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; 202 /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; 203 /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; 204 205 /** Which bits in an authentication id are used for the dataset id */ 206 private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF; 207 /** How many bits in an authentication id are used for the dataset id */ 208 private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16; 209 /** @hide The index for an undefined data set */ 210 public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF; 211 212 /** 213 * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI. 214 * 215 * @hide 216 */ 217 public static final int PENDING_UI_OPERATION_CANCEL = 1; 218 219 /** 220 * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI. 221 * 222 * @hide 223 */ 224 public static final int PENDING_UI_OPERATION_RESTORE = 2; 225 226 /** 227 * Initial state of the autofill context, set when there is no session (i.e., when 228 * {@link #mSessionId} is {@link #NO_SESSION}). 229 * 230 * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to 231 * the server. 232 * 233 * @hide 234 */ 235 public static final int STATE_UNKNOWN = 0; 236 237 /** 238 * State where the autofill context hasn't been {@link #commit() finished} nor 239 * {@link #cancel() canceled} yet. 240 * 241 * @hide 242 */ 243 public static final int STATE_ACTIVE = 1; 244 245 /** 246 * State where the autofill context was finished by the server because the autofill 247 * service could not autofill the activity. 248 * 249 * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, 250 * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). 251 * 252 * @hide 253 */ 254 public static final int STATE_FINISHED = 2; 255 256 /** 257 * State where the autofill context has been {@link #commit() finished} but the server still has 258 * a session because the Save UI hasn't been dismissed yet. 259 * 260 * @hide 261 */ 262 public static final int STATE_SHOWING_SAVE_UI = 3; 263 264 /** 265 * State where the autofill is disabled because the service cannot autofill the activity at all. 266 * 267 * <p>In this state, every call is ignored, even {@link #requestAutofill(View)} 268 * (and {@link #requestAutofill(View, int, Rect)}). 269 * 270 * @hide 271 */ 272 public static final int STATE_DISABLED_BY_SERVICE = 4; 273 274 /** 275 * Timeout in ms for calls to the field classification service. 276 * @hide 277 */ 278 public static final int FC_SERVICE_TIMEOUT = 5000; 279 280 /** 281 * Makes an authentication id from a request id and a dataset id. 282 * 283 * @param requestId The request id. 284 * @param datasetId The dataset id. 285 * @return The authentication id. 286 * @hide 287 */ 288 public static int makeAuthenticationId(int requestId, int datasetId) { 289 return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT) 290 | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK); 291 } 292 293 /** 294 * Gets the request id from an authentication id. 295 * 296 * @param authRequestId The authentication id. 297 * @return The request id. 298 * @hide 299 */ 300 public static int getRequestIdFromAuthenticationId(int authRequestId) { 301 return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT); 302 } 303 304 /** 305 * Gets the dataset id from an authentication id. 306 * 307 * @param authRequestId The authentication id. 308 * @return The dataset id. 309 * @hide 310 */ 311 public static int getDatasetIdFromAuthenticationId(int authRequestId) { 312 return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK); 313 } 314 315 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 316 317 /** 318 * There is currently no session running. 319 * {@hide} 320 */ 321 public static final int NO_SESSION = Integer.MIN_VALUE; 322 323 private final IAutoFillManager mService; 324 325 private final Object mLock = new Object(); 326 327 @GuardedBy("mLock") 328 private IAutoFillManagerClient mServiceClient; 329 330 @GuardedBy("mLock") 331 private Cleaner mServiceClientCleaner; 332 333 @GuardedBy("mLock") 334 private AutofillCallback mCallback; 335 336 private final Context mContext; 337 338 @GuardedBy("mLock") 339 private int mSessionId = NO_SESSION; 340 341 @GuardedBy("mLock") 342 private int mState = STATE_UNKNOWN; 343 344 @GuardedBy("mLock") 345 private boolean mEnabled; 346 347 /** If a view changes to this mapping the autofill operation was successful */ 348 @GuardedBy("mLock") 349 @Nullable private ParcelableMap mLastAutofilledData; 350 351 /** If view tracking is enabled, contains the tracking state */ 352 @GuardedBy("mLock") 353 @Nullable private TrackedViews mTrackedViews; 354 355 /** Views that are only tracked because they are fillable and could be anchoring the UI. */ 356 @GuardedBy("mLock") 357 @Nullable private ArraySet<AutofillId> mFillableIds; 358 359 /** 360 * Views that were already "entered" - if they're entered again when the session is not active, 361 * they're ignored 362 * */ 363 @GuardedBy("mLock") 364 @Nullable private ArraySet<AutofillId> mEnteredIds; 365 366 /** If set, session is commited when the field is clicked. */ 367 @GuardedBy("mLock") 368 @Nullable private AutofillId mSaveTriggerId; 369 370 /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ 371 @GuardedBy("mLock") 372 private boolean mOnInvisibleCalled; 373 374 /** If set, session is commited when the activity is finished; otherwise session is canceled. */ 375 @GuardedBy("mLock") 376 private boolean mSaveOnFinish; 377 378 /** If compatibility mode is enabled - this is a bridge to interact with a11y */ 379 @GuardedBy("mLock") 380 private CompatibilityBridge mCompatibilityBridge; 381 382 /** @hide */ 383 public interface AutofillClient { 384 /** 385 * Asks the client to start an authentication flow. 386 * 387 * @param authenticationId A unique id of the authentication operation. 388 * @param intent The authentication intent. 389 * @param fillInIntent The authentication fill-in intent. 390 */ 391 void autofillClientAuthenticate(int authenticationId, IntentSender intent, 392 Intent fillInIntent); 393 394 /** 395 * Tells the client this manager has state to be reset. 396 */ 397 void autofillClientResetableStateAvailable(); 398 399 /** 400 * Request showing the autofill UI. 401 * 402 * @param anchor The real view the UI needs to anchor to. 403 * @param width The width of the fill UI content. 404 * @param height The height of the fill UI content. 405 * @param virtualBounds The bounds of the virtual decendant of the anchor. 406 * @param presenter The presenter that controls the fill UI window. 407 * @return Whether the UI was shown. 408 */ 409 boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height, 410 @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter); 411 412 /** 413 * Request hiding the autofill UI. 414 * 415 * @return Whether the UI was hidden. 416 */ 417 boolean autofillClientRequestHideFillUi(); 418 419 /** 420 * Gets whether the fill UI is currenlty being shown. 421 * 422 * @return Whether the fill UI is currently being shown 423 */ 424 boolean autofillClientIsFillUiShowing(); 425 426 /** 427 * Checks if views are currently attached and visible. 428 * 429 * @return And array with {@code true} iff the view is attached or visible 430 */ 431 @NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds); 432 433 /** 434 * Checks is the client is currently visible as understood by autofill. 435 * 436 * @return {@code true} if the client is currently visible 437 */ 438 boolean autofillClientIsVisibleForAutofill(); 439 440 /** 441 * Client might disable enter/exit event e.g. when activity is paused. 442 */ 443 boolean isDisablingEnterExitEventForAutofill(); 444 445 /** 446 * Finds views by traversing the hierarchies of the client. 447 * 448 * @param autofillIds The autofill ids of the views to find 449 * 450 * @return And array containing the views (empty if no views found). 451 */ 452 @NonNull View[] autofillClientFindViewsByAutofillIdTraversal( 453 @NonNull AutofillId[] autofillIds); 454 455 /** 456 * Finds a view by traversing the hierarchies of the client. 457 * 458 * @param autofillId The autofill id of the views to find 459 * 460 * @return The view, or {@code null} if not found 461 */ 462 @Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId); 463 464 /** 465 * Finds a view by a11y id in a given client window. 466 * 467 * @param viewId The accessibility id of the views to find 468 * @param windowId The accessibility window id where to search 469 * 470 * @return The view, or {@code null} if not found 471 */ 472 @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId); 473 474 /** 475 * Runs the specified action on the UI thread. 476 */ 477 void autofillClientRunOnUiThread(Runnable action); 478 479 /** 480 * Gets the complete component name of this client. 481 */ 482 ComponentName autofillClientGetComponentName(); 483 484 /** 485 * Gets the activity token 486 */ 487 @Nullable IBinder autofillClientGetActivityToken(); 488 489 /** 490 * @return Whether compatibility mode is enabled. 491 */ 492 boolean autofillIsCompatibilityModeEnabled(); 493 } 494 495 /** 496 * @hide 497 */ 498 public AutofillManager(Context context, IAutoFillManager service) { 499 mContext = Preconditions.checkNotNull(context, "context cannot be null"); 500 mService = service; 501 } 502 503 /** 504 * @hide 505 */ 506 public void enableCompatibilityMode() { 507 synchronized (mLock) { 508 // The accessibility manager is a singleton so we may need to plug 509 // different bridge based on which activity is currently focused 510 // in the current process. Since compat would be rarely used, just 511 // create and register a new instance every time. 512 mCompatibilityBridge = new CompatibilityBridge(); 513 } 514 } 515 516 /** 517 * Restore state after activity lifecycle 518 * 519 * @param savedInstanceState The state to be restored 520 * 521 * {@hide} 522 */ 523 public void onCreate(Bundle savedInstanceState) { 524 if (!hasAutofillFeature()) { 525 return; 526 } 527 synchronized (mLock) { 528 mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); 529 530 if (isActiveLocked()) { 531 Log.w(TAG, "New session was started before onCreate()"); 532 return; 533 } 534 535 mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); 536 mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); 537 538 if (mSessionId != NO_SESSION) { 539 ensureServiceClientAddedIfNeededLocked(); 540 541 final AutofillClient client = getClient(); 542 if (client != null) { 543 try { 544 final boolean sessionWasRestored = mService.restoreSession(mSessionId, 545 client.autofillClientGetActivityToken(), 546 mServiceClient.asBinder()); 547 548 if (!sessionWasRestored) { 549 Log.w(TAG, "Session " + mSessionId + " could not be restored"); 550 mSessionId = NO_SESSION; 551 mState = STATE_UNKNOWN; 552 } else { 553 if (sDebug) { 554 Log.d(TAG, "session " + mSessionId + " was restored"); 555 } 556 557 client.autofillClientResetableStateAvailable(); 558 } 559 } catch (RemoteException e) { 560 Log.e(TAG, "Could not figure out if there was an autofill session", e); 561 } 562 } 563 } 564 } 565 } 566 567 /** 568 * Called once the client becomes visible. 569 * 570 * @see AutofillClient#autofillClientIsVisibleForAutofill() 571 * 572 * {@hide} 573 */ 574 public void onVisibleForAutofill() { 575 // This gets called when the client just got visible at which point the visibility 576 // of the tracked views may not have been computed (due to a pending layout, etc). 577 // While generally we have no way to know when the UI has settled. We will evaluate 578 // the tracked views state at the end of next frame to guarantee that everything 579 // that may need to be laid out is laid out. 580 Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { 581 synchronized (mLock) { 582 if (mEnabled && isActiveLocked() && mTrackedViews != null) { 583 mTrackedViews.onVisibleForAutofillChangedLocked(); 584 } 585 } 586 }, null); 587 } 588 589 /** 590 * Called once the client becomes invisible. 591 * 592 * @see AutofillClient#autofillClientIsVisibleForAutofill() 593 * 594 * {@hide} 595 */ 596 public void onInvisibleForAutofill() { 597 synchronized (mLock) { 598 mOnInvisibleCalled = true; 599 } 600 } 601 602 /** 603 * Save state before activity lifecycle 604 * 605 * @param outState Place to store the state 606 * 607 * {@hide} 608 */ 609 public void onSaveInstanceState(Bundle outState) { 610 if (!hasAutofillFeature()) { 611 return; 612 } 613 synchronized (mLock) { 614 if (mSessionId != NO_SESSION) { 615 outState.putInt(SESSION_ID_TAG, mSessionId); 616 } 617 if (mState != STATE_UNKNOWN) { 618 outState.putInt(STATE_TAG, mState); 619 } 620 if (mLastAutofilledData != null) { 621 outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); 622 } 623 } 624 } 625 626 /** 627 * @hide 628 */ 629 @GuardedBy("mLock") 630 public boolean isCompatibilityModeEnabledLocked() { 631 return mCompatibilityBridge != null; 632 } 633 634 /** 635 * Checks whether autofill is enabled for the current user. 636 * 637 * <p>Typically used to determine whether the option to explicitly request autofill should 638 * be offered - see {@link #requestAutofill(View)}. 639 * 640 * @return whether autofill is enabled for the current user. 641 */ 642 public boolean isEnabled() { 643 if (!hasAutofillFeature()) { 644 return false; 645 } 646 synchronized (mLock) { 647 if (isDisabledByServiceLocked()) { 648 return false; 649 } 650 ensureServiceClientAddedIfNeededLocked(); 651 return mEnabled; 652 } 653 } 654 655 /** 656 * Should always be called from {@link AutofillService#getFillEventHistory()}. 657 * 658 * @hide 659 */ 660 @Nullable public FillEventHistory getFillEventHistory() { 661 try { 662 return mService.getFillEventHistory(); 663 } catch (RemoteException e) { 664 e.rethrowFromSystemServer(); 665 return null; 666 } 667 } 668 669 /** 670 * Explicitly requests a new autofill context. 671 * 672 * <p>Normally, the autofill context is automatically started if necessary when 673 * {@link #notifyViewEntered(View)} is called, but this method should be used in the 674 * cases where it must be explicitly started. For example, when the view offers an AUTOFILL 675 * option on its contextual overflow menu, and the user selects it. 676 * 677 * @param view view requesting the new autofill context. 678 */ 679 public void requestAutofill(@NonNull View view) { 680 notifyViewEntered(view, FLAG_MANUAL_REQUEST); 681 } 682 683 /** 684 * Explicitly requests a new autofill context for virtual views. 685 * 686 * <p>Normally, the autofill context is automatically started if necessary when 687 * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the 688 * cases where it must be explicitly started. For example, when the virtual view offers an 689 * AUTOFILL option on its contextual overflow menu, and the user selects it. 690 * 691 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 692 * parent view uses {@code bounds} to draw the virtual view inside its Canvas, 693 * the absolute bounds could be calculated by: 694 * 695 * <pre class="prettyprint"> 696 * int offset[] = new int[2]; 697 * getLocationOnScreen(offset); 698 * Rect absBounds = new Rect(bounds.left + offset[0], 699 * bounds.top + offset[1], 700 * bounds.right + offset[0], bounds.bottom + offset[1]); 701 * </pre> 702 * 703 * @param view the virtual view parent. 704 * @param virtualId id identifying the virtual child inside the parent view. 705 * @param absBounds absolute boundaries of the virtual view in the screen. 706 */ 707 public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 708 notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST); 709 } 710 711 /** 712 * Called when a {@link View} that supports autofill is entered. 713 * 714 * @param view {@link View} that was entered. 715 */ 716 public void notifyViewEntered(@NonNull View view) { 717 notifyViewEntered(view, 0); 718 } 719 720 @GuardedBy("mLock") 721 private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) { 722 if (isDisabledByServiceLocked()) { 723 if (sVerbose) { 724 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 725 + ") on state " + getStateAsStringLocked() + " because disabled by svc"); 726 } 727 return true; 728 } 729 if (isFinishedLocked()) { 730 // Session already finished: ignore if automatic request and view already entered 731 if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null 732 && mEnteredIds.contains(id)) { 733 if (sVerbose) { 734 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id 735 + ") on state " + getStateAsStringLocked() 736 + " because view was already entered: " + mEnteredIds); 737 } 738 return true; 739 } 740 } 741 if (sVerbose) { 742 Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + id 743 + ", state " + getStateAsStringLocked() + ", enteredIds=" + mEnteredIds); 744 } 745 return false; 746 } 747 748 private boolean isClientVisibleForAutofillLocked() { 749 final AutofillClient client = getClient(); 750 return client != null && client.autofillClientIsVisibleForAutofill(); 751 } 752 753 private boolean isClientDisablingEnterExitEvent() { 754 final AutofillClient client = getClient(); 755 return client != null && client.isDisablingEnterExitEventForAutofill(); 756 } 757 758 private void notifyViewEntered(@NonNull View view, int flags) { 759 if (!hasAutofillFeature()) { 760 return; 761 } 762 AutofillCallback callback; 763 synchronized (mLock) { 764 callback = notifyViewEnteredLocked(view, flags); 765 } 766 767 if (callback != null) { 768 mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 769 } 770 } 771 772 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ 773 @GuardedBy("mLock") 774 private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { 775 final AutofillId id = getAutofillId(view); 776 if (shouldIgnoreViewEnteredLocked(id, flags)) return null; 777 778 AutofillCallback callback = null; 779 780 ensureServiceClientAddedIfNeededLocked(); 781 782 if (!mEnabled) { 783 if (mCallback != null) { 784 callback = mCallback; 785 } 786 } else { 787 // don't notify entered when Activity is already in background 788 if (!isClientDisablingEnterExitEvent()) { 789 final AutofillValue value = view.getAutofillValue(); 790 791 if (!isActiveLocked()) { 792 // Starts new session. 793 startSessionLocked(id, null, value, flags); 794 } else { 795 // Update focus on existing session. 796 updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags); 797 } 798 addEnteredIdLocked(id); 799 } 800 } 801 return callback; 802 } 803 804 /** 805 * Called when a {@link View} that supports autofill is exited. 806 * 807 * @param view {@link View} that was exited. 808 */ 809 public void notifyViewExited(@NonNull View view) { 810 if (!hasAutofillFeature()) { 811 return; 812 } 813 synchronized (mLock) { 814 notifyViewExitedLocked(view); 815 } 816 } 817 818 @GuardedBy("mLock") 819 void notifyViewExitedLocked(@NonNull View view) { 820 ensureServiceClientAddedIfNeededLocked(); 821 822 if (mEnabled && isActiveLocked()) { 823 // dont notify exited when Activity is already in background 824 if (!isClientDisablingEnterExitEvent()) { 825 final AutofillId id = getAutofillId(view); 826 827 // Update focus on existing session. 828 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 829 } 830 } 831 } 832 833 /** 834 * Called when a {@link View view's} visibility changed. 835 * 836 * @param view {@link View} that was exited. 837 * @param isVisible visible if the view is visible in the view hierarchy. 838 */ 839 public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) { 840 notifyViewVisibilityChangedInternal(view, 0, isVisible, false); 841 } 842 843 /** 844 * Called when a virtual view's visibility changed. 845 * 846 * @param view {@link View} that was exited. 847 * @param virtualId id identifying the virtual child inside the parent view. 848 * @param isVisible visible if the view is visible in the view hierarchy. 849 */ 850 public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) { 851 notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true); 852 } 853 854 /** 855 * Called when a view/virtual view's visibility changed. 856 * 857 * @param view {@link View} that was exited. 858 * @param virtualId id identifying the virtual child inside the parent view. 859 * @param isVisible visible if the view is visible in the view hierarchy. 860 * @param virtual Whether the view is virtual. 861 */ 862 private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId, 863 boolean isVisible, boolean virtual) { 864 synchronized (mLock) { 865 if (mEnabled && isActiveLocked()) { 866 final AutofillId id = virtual ? getAutofillId(view, virtualId) 867 : view.getAutofillId(); 868 if (!isVisible && mFillableIds != null) { 869 if (mFillableIds.contains(id)) { 870 if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible"); 871 requestHideFillUi(id, view); 872 } 873 } 874 if (mTrackedViews != null) { 875 mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); 876 } 877 } 878 } 879 } 880 881 /** 882 * Called when a virtual view that supports autofill is entered. 883 * 884 * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the 885 * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas, 886 * the absolute bounds could be calculated by: 887 * 888 * <pre class="prettyprint"> 889 * int offset[] = new int[2]; 890 * getLocationOnScreen(offset); 891 * Rect absBounds = new Rect(bounds.left + offset[0], 892 * bounds.top + offset[1], 893 * bounds.right + offset[0], bounds.bottom + offset[1]); 894 * </pre> 895 * 896 * @param view the virtual view parent. 897 * @param virtualId id identifying the virtual child inside the parent view. 898 * @param absBounds absolute boundaries of the virtual view in the screen. 899 */ 900 public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) { 901 notifyViewEntered(view, virtualId, absBounds, 0); 902 } 903 904 private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) { 905 if (!hasAutofillFeature()) { 906 return; 907 } 908 AutofillCallback callback; 909 synchronized (mLock) { 910 callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); 911 } 912 913 if (callback != null) { 914 callback.onAutofillEvent(view, virtualId, 915 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 916 } 917 } 918 919 /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ 920 @GuardedBy("mLock") 921 private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, 922 int flags) { 923 final AutofillId id = getAutofillId(view, virtualId); 924 AutofillCallback callback = null; 925 if (shouldIgnoreViewEnteredLocked(id, flags)) return callback; 926 927 ensureServiceClientAddedIfNeededLocked(); 928 929 if (!mEnabled) { 930 if (mCallback != null) { 931 callback = mCallback; 932 } 933 } else { 934 // don't notify entered when Activity is already in background 935 if (!isClientDisablingEnterExitEvent()) { 936 if (!isActiveLocked()) { 937 // Starts new session. 938 startSessionLocked(id, bounds, null, flags); 939 } else { 940 // Update focus on existing session. 941 updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags); 942 } 943 addEnteredIdLocked(id); 944 } 945 } 946 return callback; 947 } 948 949 @GuardedBy("mLock") 950 private void addEnteredIdLocked(@NonNull AutofillId id) { 951 if (mEnteredIds == null) { 952 mEnteredIds = new ArraySet<>(1); 953 } 954 mEnteredIds.add(id); 955 } 956 957 /** 958 * Called when a virtual view that supports autofill is exited. 959 * 960 * @param view the virtual view parent. 961 * @param virtualId id identifying the virtual child inside the parent view. 962 */ 963 public void notifyViewExited(@NonNull View view, int virtualId) { 964 if (!hasAutofillFeature()) { 965 return; 966 } 967 synchronized (mLock) { 968 notifyViewExitedLocked(view, virtualId); 969 } 970 } 971 972 @GuardedBy("mLock") 973 private void notifyViewExitedLocked(@NonNull View view, int virtualId) { 974 ensureServiceClientAddedIfNeededLocked(); 975 976 if (mEnabled && isActiveLocked()) { 977 // don't notify exited when Activity is already in background 978 if (!isClientDisablingEnterExitEvent()) { 979 final AutofillId id = getAutofillId(view, virtualId); 980 981 // Update focus on existing session. 982 updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); 983 } 984 } 985 } 986 987 /** 988 * Called to indicate the value of an autofillable {@link View} changed. 989 * 990 * @param view view whose value changed. 991 */ 992 public void notifyValueChanged(View view) { 993 if (!hasAutofillFeature()) { 994 return; 995 } 996 AutofillId id = null; 997 boolean valueWasRead = false; 998 AutofillValue value = null; 999 1000 synchronized (mLock) { 1001 // If the session is gone some fields might still be highlighted, hence we have to 1002 // remove the isAutofilled property even if no sessions are active. 1003 if (mLastAutofilledData == null) { 1004 view.setAutofilled(false); 1005 } else { 1006 id = getAutofillId(view); 1007 if (mLastAutofilledData.containsKey(id)) { 1008 value = view.getAutofillValue(); 1009 valueWasRead = true; 1010 1011 if (Objects.equals(mLastAutofilledData.get(id), value)) { 1012 view.setAutofilled(true); 1013 } else { 1014 view.setAutofilled(false); 1015 mLastAutofilledData.remove(id); 1016 } 1017 } else { 1018 view.setAutofilled(false); 1019 } 1020 } 1021 1022 if (!mEnabled || !isActiveLocked()) { 1023 if (sVerbose) { 1024 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() 1025 + "): ignoring on state " + getStateAsStringLocked()); 1026 } 1027 return; 1028 } 1029 1030 if (id == null) { 1031 id = getAutofillId(view); 1032 } 1033 1034 if (!valueWasRead) { 1035 value = view.getAutofillValue(); 1036 } 1037 1038 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 1039 } 1040 } 1041 1042 /** 1043 * Called to indicate the value of an autofillable virtual view has changed. 1044 * 1045 * @param view the virtual view parent. 1046 * @param virtualId id identifying the virtual child inside the parent view. 1047 * @param value new value of the child. 1048 */ 1049 public void notifyValueChanged(View view, int virtualId, AutofillValue value) { 1050 if (!hasAutofillFeature()) { 1051 return; 1052 } 1053 synchronized (mLock) { 1054 if (!mEnabled || !isActiveLocked()) { 1055 if (sVerbose) { 1056 Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId 1057 + "): ignoring on state " + getStateAsStringLocked()); 1058 } 1059 return; 1060 } 1061 1062 final AutofillId id = getAutofillId(view, virtualId); 1063 updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); 1064 } 1065 } 1066 1067 /** 1068 * Called when a {@link View} is clicked. Currently only used by views that should trigger save. 1069 * 1070 * @hide 1071 */ 1072 public void notifyViewClicked(View view) { 1073 final AutofillId id = view.getAutofillId(); 1074 1075 if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); 1076 1077 synchronized (mLock) { 1078 if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { 1079 if (sDebug) Log.d(TAG, "triggering commit by click of " + id); 1080 commitLocked(); 1081 mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED, 1082 mContext.getPackageName()); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Called by {@link android.app.Activity} to commit or cancel the session on finish. 1089 * 1090 * @hide 1091 */ 1092 public void onActivityFinished() { 1093 if (!hasAutofillFeature()) { 1094 return; 1095 } 1096 synchronized (mLock) { 1097 if (mSaveOnFinish) { 1098 if (sDebug) Log.d(TAG, "Committing session on finish() as requested by service"); 1099 commitLocked(); 1100 } else { 1101 if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service"); 1102 cancelLocked(); 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Called to indicate the current autofill context should be commited. 1109 * 1110 * <p>This method is typically called by {@link View Views} that manage virtual views; for 1111 * example, when the view is rendering an {@code HTML} page with a form and virtual views 1112 * that represent the HTML elements, it should call this method after the form is submitted and 1113 * another page is rendered. 1114 * 1115 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 1116 * methods such as {@link android.app.Activity#finish()}. 1117 */ 1118 public void commit() { 1119 if (!hasAutofillFeature()) { 1120 return; 1121 } 1122 synchronized (mLock) { 1123 commitLocked(); 1124 } 1125 } 1126 1127 @GuardedBy("mLock") 1128 private void commitLocked() { 1129 if (!mEnabled && !isActiveLocked()) { 1130 return; 1131 } 1132 finishSessionLocked(); 1133 } 1134 1135 /** 1136 * Called to indicate the current autofill context should be cancelled. 1137 * 1138 * <p>This method is typically called by {@link View Views} that manage virtual views; for 1139 * example, when the view is rendering an {@code HTML} page with a form and virtual views 1140 * that represent the HTML elements, it should call this method if the user does not post the 1141 * form but moves to another form in this page. 1142 * 1143 * <p><b>Note:</b> This method does not need to be called on regular application lifecycle 1144 * methods such as {@link android.app.Activity#finish()}. 1145 */ 1146 public void cancel() { 1147 if (sVerbose) Log.v(TAG, "cancel() called by app"); 1148 if (!hasAutofillFeature()) { 1149 return; 1150 } 1151 synchronized (mLock) { 1152 cancelLocked(); 1153 } 1154 } 1155 1156 @GuardedBy("mLock") 1157 private void cancelLocked() { 1158 if (!mEnabled && !isActiveLocked()) { 1159 return; 1160 } 1161 cancelSessionLocked(); 1162 } 1163 1164 /** @hide */ 1165 public void disableOwnedAutofillServices() { 1166 disableAutofillServices(); 1167 } 1168 1169 /** 1170 * If the app calling this API has enabled autofill services they 1171 * will be disabled. 1172 */ 1173 public void disableAutofillServices() { 1174 if (!hasAutofillFeature()) { 1175 return; 1176 } 1177 try { 1178 mService.disableOwnedAutofillServices(mContext.getUserId()); 1179 } catch (RemoteException e) { 1180 throw e.rethrowFromSystemServer(); 1181 } 1182 } 1183 1184 /** 1185 * Returns {@code true} if the calling application provides a {@link AutofillService} that is 1186 * enabled for the current user, or {@code false} otherwise. 1187 */ 1188 public boolean hasEnabledAutofillServices() { 1189 if (mService == null) return false; 1190 1191 try { 1192 return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName()); 1193 } catch (RemoteException e) { 1194 throw e.rethrowFromSystemServer(); 1195 } 1196 } 1197 1198 /** 1199 * Returns the component name of the {@link AutofillService} that is enabled for the current 1200 * user. 1201 */ 1202 @Nullable 1203 public ComponentName getAutofillServiceComponentName() { 1204 if (mService == null) return null; 1205 1206 try { 1207 return mService.getAutofillServiceComponentName(); 1208 } catch (RemoteException e) { 1209 throw e.rethrowFromSystemServer(); 1210 } 1211 } 1212 1213 /** 1214 * Gets the id of the {@link UserData} used for 1215 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1216 * 1217 * <p>This method is useful when the service must check the status of the {@link UserData} in 1218 * the device without fetching the whole object. 1219 * 1220 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1221 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1222 * the user. 1223 * 1224 * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)} 1225 * or {@code null} if it was reset or if the caller currently does not have an enabled autofill 1226 * service for the user. 1227 */ 1228 @Nullable public String getUserDataId() { 1229 try { 1230 return mService.getUserDataId(); 1231 } catch (RemoteException e) { 1232 e.rethrowFromSystemServer(); 1233 return null; 1234 } 1235 } 1236 1237 /** 1238 * Gets the user data used for 1239 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1240 * 1241 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1242 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1243 * the user. 1244 * 1245 * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was 1246 * reset or if the caller currently does not have an enabled autofill service for the user. 1247 */ 1248 @Nullable public UserData getUserData() { 1249 try { 1250 return mService.getUserData(); 1251 } catch (RemoteException e) { 1252 e.rethrowFromSystemServer(); 1253 return null; 1254 } 1255 } 1256 1257 /** 1258 * Sets the {@link UserData} used for 1259 * <a href="AutofillService.html#FieldClassification">field classification</a> 1260 * 1261 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1262 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1263 * the user. 1264 */ 1265 public void setUserData(@Nullable UserData userData) { 1266 try { 1267 mService.setUserData(userData); 1268 } catch (RemoteException e) { 1269 e.rethrowFromSystemServer(); 1270 } 1271 } 1272 1273 /** 1274 * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is 1275 * enabled. 1276 * 1277 * <p>As field classification is an expensive operation, it could be disabled, either 1278 * temporarily (for example, because the service exceeded a rate-limit threshold) or 1279 * permanently (for example, because the device is a low-level device). 1280 * 1281 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1282 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1283 * the user. 1284 */ 1285 public boolean isFieldClassificationEnabled() { 1286 try { 1287 return mService.isFieldClassificationEnabled(); 1288 } catch (RemoteException e) { 1289 e.rethrowFromSystemServer(); 1290 return false; 1291 } 1292 } 1293 1294 /** 1295 * Gets the name of the default algorithm used for 1296 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1297 * 1298 * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not 1299 * set. 1300 * 1301 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1302 * and it's ignored if the caller currently doesn't have an enabled autofill service for 1303 * the user. 1304 */ 1305 @Nullable 1306 public String getDefaultFieldClassificationAlgorithm() { 1307 try { 1308 return mService.getDefaultFieldClassificationAlgorithm(); 1309 } catch (RemoteException e) { 1310 e.rethrowFromSystemServer(); 1311 return null; 1312 } 1313 } 1314 1315 /** 1316 * Gets the name of all algorithms currently available for 1317 * <a href="AutofillService.html#FieldClassification">field classification</a>. 1318 * 1319 * <p><b>Note:</b> This method should only be called by an app providing an autofill service, 1320 * and it returns an empty list if the caller currently doesn't have an enabled autofill service 1321 * for the user. 1322 */ 1323 @NonNull 1324 public List<String> getAvailableFieldClassificationAlgorithms() { 1325 final String[] algorithms; 1326 try { 1327 algorithms = mService.getAvailableFieldClassificationAlgorithms(); 1328 return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); 1329 } catch (RemoteException e) { 1330 e.rethrowFromSystemServer(); 1331 return null; 1332 } 1333 } 1334 1335 /** 1336 * Returns {@code true} if autofill is supported by the current device and 1337 * is supported for this user. 1338 * 1339 * <p>Autofill is typically supported, but it could be unsupported in cases like: 1340 * <ol> 1341 * <li>Low-end devices. 1342 * <li>Device policy rules that forbid its usage. 1343 * </ol> 1344 */ 1345 public boolean isAutofillSupported() { 1346 if (mService == null) return false; 1347 1348 try { 1349 return mService.isServiceSupported(mContext.getUserId()); 1350 } catch (RemoteException e) { 1351 throw e.rethrowFromSystemServer(); 1352 } 1353 } 1354 1355 // Note: don't need to use locked suffix because mContext is final. 1356 private AutofillClient getClient() { 1357 final AutofillClient client = mContext.getAutofillClient(); 1358 if (client == null && sDebug) { 1359 Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " 1360 + mContext); 1361 } 1362 return client; 1363 } 1364 1365 /** @hide */ 1366 public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { 1367 if (!hasAutofillFeature()) { 1368 return; 1369 } 1370 // TODO: the result code is being ignored, so this method is not reliably 1371 // handling the cases where it's not RESULT_OK: it works fine if the service does not 1372 // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the 1373 // service set the extra and returned RESULT_CANCELED... 1374 1375 if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); 1376 1377 synchronized (mLock) { 1378 if (!isActiveLocked()) { 1379 return; 1380 } 1381 // If authenticate activity closes itself during onCreate(), there is no onStop/onStart 1382 // of app activity. We enforce enter event to re-show fill ui in such case. 1383 // CTS example: 1384 // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt 1385 // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt 1386 if (!mOnInvisibleCalled && focusView != null 1387 && focusView.canNotifyAutofillEnterExitEvent()) { 1388 notifyViewExitedLocked(focusView); 1389 notifyViewEnteredLocked(focusView, 0); 1390 } 1391 if (data == null) { 1392 // data is set to null when result is not RESULT_OK 1393 return; 1394 } 1395 1396 final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); 1397 final Bundle responseData = new Bundle(); 1398 responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); 1399 final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); 1400 if (newClientState != null) { 1401 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); 1402 } 1403 try { 1404 mService.setAuthenticationResult(responseData, mSessionId, authenticationId, 1405 mContext.getUserId()); 1406 } catch (RemoteException e) { 1407 Log.e(TAG, "Error delivering authentication result", e); 1408 } 1409 } 1410 } 1411 1412 private static AutofillId getAutofillId(View view) { 1413 return new AutofillId(view.getAutofillViewId()); 1414 } 1415 1416 private static AutofillId getAutofillId(View parent, int virtualId) { 1417 return new AutofillId(parent.getAutofillViewId(), virtualId); 1418 } 1419 1420 @GuardedBy("mLock") 1421 private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, 1422 @NonNull AutofillValue value, int flags) { 1423 if (sVerbose) { 1424 Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value 1425 + ", flags=" + flags + ", state=" + getStateAsStringLocked() 1426 + ", compatMode=" + isCompatibilityModeEnabledLocked() 1427 + ", enteredIds=" + mEnteredIds); 1428 } 1429 if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { 1430 if (sVerbose) { 1431 Log.v(TAG, "not automatically starting session for " + id 1432 + " on state " + getStateAsStringLocked() + " and flags " + flags); 1433 } 1434 return; 1435 } 1436 try { 1437 final AutofillClient client = getClient(); 1438 if (client == null) return; // NOTE: getClient() already logged it.. 1439 1440 mSessionId = mService.startSession(client.autofillClientGetActivityToken(), 1441 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 1442 mCallback != null, flags, client.autofillClientGetComponentName()); 1443 if (mSessionId != NO_SESSION) { 1444 mState = STATE_ACTIVE; 1445 } 1446 client.autofillClientResetableStateAvailable(); 1447 } catch (RemoteException e) { 1448 throw e.rethrowFromSystemServer(); 1449 } 1450 } 1451 1452 @GuardedBy("mLock") 1453 private void finishSessionLocked() { 1454 if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); 1455 1456 if (!isActiveLocked()) return; 1457 1458 try { 1459 mService.finishSession(mSessionId, mContext.getUserId()); 1460 } catch (RemoteException e) { 1461 throw e.rethrowFromSystemServer(); 1462 } 1463 1464 resetSessionLocked(/* resetEnteredIds= */ true); 1465 } 1466 1467 @GuardedBy("mLock") 1468 private void cancelSessionLocked() { 1469 if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); 1470 1471 if (!isActiveLocked()) return; 1472 1473 try { 1474 mService.cancelSession(mSessionId, mContext.getUserId()); 1475 } catch (RemoteException e) { 1476 throw e.rethrowFromSystemServer(); 1477 } 1478 1479 resetSessionLocked(/* resetEnteredIds= */ true); 1480 } 1481 1482 @GuardedBy("mLock") 1483 private void resetSessionLocked(boolean resetEnteredIds) { 1484 mSessionId = NO_SESSION; 1485 mState = STATE_UNKNOWN; 1486 mTrackedViews = null; 1487 mFillableIds = null; 1488 mSaveTriggerId = null; 1489 if (resetEnteredIds) { 1490 mEnteredIds = null; 1491 } 1492 } 1493 1494 @GuardedBy("mLock") 1495 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, 1496 int flags) { 1497 if (sVerbose) { 1498 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds 1499 + ", value=" + value + ", action=" + action + ", flags=" + flags); 1500 } 1501 boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0; 1502 1503 try { 1504 if (restartIfNecessary) { 1505 final AutofillClient client = getClient(); 1506 if (client == null) return; // NOTE: getClient() already logd it.. 1507 1508 final int newId = mService.updateOrRestartSession( 1509 client.autofillClientGetActivityToken(), 1510 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 1511 mCallback != null, flags, client.autofillClientGetComponentName(), 1512 mSessionId, action); 1513 if (newId != mSessionId) { 1514 if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); 1515 mSessionId = newId; 1516 mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; 1517 client.autofillClientResetableStateAvailable(); 1518 } 1519 } else { 1520 mService.updateSession(mSessionId, id, bounds, value, action, flags, 1521 mContext.getUserId()); 1522 } 1523 1524 } catch (RemoteException e) { 1525 throw e.rethrowFromSystemServer(); 1526 } 1527 } 1528 1529 @GuardedBy("mLock") 1530 private void ensureServiceClientAddedIfNeededLocked() { 1531 if (getClient() == null) { 1532 return; 1533 } 1534 1535 if (mServiceClient == null) { 1536 mServiceClient = new AutofillManagerClient(this); 1537 try { 1538 final int userId = mContext.getUserId(); 1539 final int flags = mService.addClient(mServiceClient, userId); 1540 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; 1541 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; 1542 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; 1543 final IAutoFillManager service = mService; 1544 final IAutoFillManagerClient serviceClient = mServiceClient; 1545 mServiceClientCleaner = Cleaner.create(this, () -> { 1546 try { 1547 service.removeClient(serviceClient, userId); 1548 } catch (RemoteException e) { 1549 } 1550 }); 1551 } catch (RemoteException e) { 1552 throw e.rethrowFromSystemServer(); 1553 } 1554 } 1555 } 1556 1557 /** 1558 * Registers a {@link AutofillCallback} to receive autofill events. 1559 * 1560 * @param callback callback to receive events. 1561 */ 1562 public void registerCallback(@Nullable AutofillCallback callback) { 1563 if (!hasAutofillFeature()) { 1564 return; 1565 } 1566 synchronized (mLock) { 1567 if (callback == null) return; 1568 1569 final boolean hadCallback = mCallback != null; 1570 mCallback = callback; 1571 1572 if (!hadCallback) { 1573 try { 1574 mService.setHasCallback(mSessionId, mContext.getUserId(), true); 1575 } catch (RemoteException e) { 1576 throw e.rethrowFromSystemServer(); 1577 } 1578 } 1579 } 1580 } 1581 1582 /** 1583 * Unregisters a {@link AutofillCallback} to receive autofill events. 1584 * 1585 * @param callback callback to stop receiving events. 1586 */ 1587 public void unregisterCallback(@Nullable AutofillCallback callback) { 1588 if (!hasAutofillFeature()) { 1589 return; 1590 } 1591 synchronized (mLock) { 1592 if (callback == null || mCallback == null || callback != mCallback) return; 1593 1594 mCallback = null; 1595 1596 try { 1597 mService.setHasCallback(mSessionId, mContext.getUserId(), false); 1598 } catch (RemoteException e) { 1599 throw e.rethrowFromSystemServer(); 1600 } 1601 } 1602 } 1603 1604 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 1605 Rect anchorBounds, IAutofillWindowPresenter presenter) { 1606 final View anchor = findView(id); 1607 if (anchor == null) { 1608 return; 1609 } 1610 1611 AutofillCallback callback = null; 1612 synchronized (mLock) { 1613 if (mSessionId == sessionId) { 1614 AutofillClient client = getClient(); 1615 1616 if (client != null) { 1617 if (client.autofillClientRequestShowFillUi(anchor, width, height, 1618 anchorBounds, presenter) && mCallback != null) { 1619 callback = mCallback; 1620 } 1621 } 1622 } 1623 } 1624 1625 if (callback != null) { 1626 if (id.isVirtual()) { 1627 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1628 AutofillCallback.EVENT_INPUT_SHOWN); 1629 } else { 1630 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); 1631 } 1632 } 1633 } 1634 1635 private void authenticate(int sessionId, int authenticationId, IntentSender intent, 1636 Intent fillInIntent) { 1637 synchronized (mLock) { 1638 if (sessionId == mSessionId) { 1639 final AutofillClient client = getClient(); 1640 if (client != null) { 1641 // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() 1642 // before onAuthenticationResult() 1643 mOnInvisibleCalled = false; 1644 client.autofillClientAuthenticate(authenticationId, intent, fillInIntent); 1645 } 1646 } 1647 } 1648 } 1649 1650 /** @hide */ 1651 public static final int SET_STATE_FLAG_ENABLED = 0x01; 1652 /** @hide */ 1653 public static final int SET_STATE_FLAG_RESET_SESSION = 0x02; 1654 /** @hide */ 1655 public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04; 1656 /** @hide */ 1657 public static final int SET_STATE_FLAG_DEBUG = 0x08; 1658 /** @hide */ 1659 public static final int SET_STATE_FLAG_VERBOSE = 0x10; 1660 1661 private void setState(int flags) { 1662 if (sVerbose) Log.v(TAG, "setState(" + flags + ")"); 1663 synchronized (mLock) { 1664 mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; 1665 if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { 1666 // Reset the session state 1667 resetSessionLocked(/* resetEnteredIds= */ true); 1668 } 1669 if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { 1670 // Reset connection to system 1671 mServiceClient = null; 1672 if (mServiceClientCleaner != null) { 1673 mServiceClientCleaner.clean(); 1674 mServiceClientCleaner = null; 1675 } 1676 } 1677 } 1678 sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; 1679 sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; 1680 } 1681 1682 /** 1683 * Sets a view as autofilled if the current value is the {code targetValue}. 1684 * 1685 * @param view The view that is to be autofilled 1686 * @param targetValue The value we want to fill into view 1687 */ 1688 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { 1689 AutofillValue currentValue = view.getAutofillValue(); 1690 if (Objects.equals(currentValue, targetValue)) { 1691 synchronized (mLock) { 1692 if (mLastAutofilledData == null) { 1693 mLastAutofilledData = new ParcelableMap(1); 1694 } 1695 mLastAutofilledData.put(getAutofillId(view), targetValue); 1696 } 1697 view.setAutofilled(true); 1698 } 1699 } 1700 1701 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 1702 synchronized (mLock) { 1703 if (sessionId != mSessionId) { 1704 return; 1705 } 1706 1707 final AutofillClient client = getClient(); 1708 if (client == null) { 1709 return; 1710 } 1711 1712 final int itemCount = ids.size(); 1713 int numApplied = 0; 1714 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; 1715 final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( 1716 Helper.toArray(ids)); 1717 1718 for (int i = 0; i < itemCount; i++) { 1719 final AutofillId id = ids.get(i); 1720 final AutofillValue value = values.get(i); 1721 final int viewId = id.getViewId(); 1722 final View view = views[i]; 1723 if (view == null) { 1724 Log.w(TAG, "autofill(): no View with id " + viewId); 1725 continue; 1726 } 1727 if (id.isVirtual()) { 1728 if (virtualValues == null) { 1729 // Most likely there will be just one view with virtual children. 1730 virtualValues = new ArrayMap<>(1); 1731 } 1732 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); 1733 if (valuesByParent == null) { 1734 // We don't know the size yet, but usually it will be just a few fields... 1735 valuesByParent = new SparseArray<>(5); 1736 virtualValues.put(view, valuesByParent); 1737 } 1738 valuesByParent.put(id.getVirtualChildId(), value); 1739 } else { 1740 // Mark the view as to be autofilled with 'value' 1741 if (mLastAutofilledData == null) { 1742 mLastAutofilledData = new ParcelableMap(itemCount - i); 1743 } 1744 mLastAutofilledData.put(id, value); 1745 1746 view.autofill(value); 1747 1748 // Set as autofilled if the values match now, e.g. when the value was updated 1749 // synchronously. 1750 // If autofill happens async, the view is set to autofilled in 1751 // notifyValueChanged. 1752 setAutofilledIfValuesIs(view, value); 1753 1754 numApplied++; 1755 } 1756 } 1757 1758 if (virtualValues != null) { 1759 for (int i = 0; i < virtualValues.size(); i++) { 1760 final View parent = virtualValues.keyAt(i); 1761 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); 1762 parent.autofill(childrenValues); 1763 numApplied += childrenValues.size(); 1764 } 1765 } 1766 1767 final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED) 1768 .setPackageName(mContext.getPackageName()) 1769 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) 1770 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); 1771 mMetricsLogger.write(log); 1772 } 1773 } 1774 1775 /** 1776 * Set the tracked views. 1777 * 1778 * @param trackedIds The views to be tracked. 1779 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. 1780 * @param saveOnFinish Finish the session once the activity is finished. 1781 * @param fillableIds Views that might anchor FillUI. 1782 * @param saveTriggerId View that when clicked triggers commit(). 1783 */ 1784 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, 1785 boolean saveOnAllViewsInvisible, boolean saveOnFinish, 1786 @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { 1787 synchronized (mLock) { 1788 if (mEnabled && mSessionId == sessionId) { 1789 if (saveOnAllViewsInvisible) { 1790 mTrackedViews = new TrackedViews(trackedIds); 1791 } else { 1792 mTrackedViews = null; 1793 } 1794 mSaveOnFinish = saveOnFinish; 1795 if (fillableIds != null) { 1796 if (mFillableIds == null) { 1797 mFillableIds = new ArraySet<>(fillableIds.length); 1798 } 1799 for (AutofillId id : fillableIds) { 1800 mFillableIds.add(id); 1801 } 1802 if (sVerbose) { 1803 Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds 1804 + ", mFillableIds" + mFillableIds); 1805 } 1806 } 1807 1808 if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { 1809 // Turn off trigger on previous view id. 1810 setNotifyOnClickLocked(mSaveTriggerId, false); 1811 } 1812 1813 if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { 1814 // Turn on trigger on new view id. 1815 mSaveTriggerId = saveTriggerId; 1816 setNotifyOnClickLocked(mSaveTriggerId, true); 1817 } 1818 } 1819 } 1820 } 1821 1822 private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { 1823 final View view = findView(id); 1824 if (view == null) { 1825 Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); 1826 return; 1827 } 1828 view.setNotifyAutofillManagerOnClick(notify); 1829 } 1830 1831 private void setSaveUiState(int sessionId, boolean shown) { 1832 if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); 1833 synchronized (mLock) { 1834 if (mSessionId != NO_SESSION) { 1835 // Race condition: app triggered a new session after the previous session was 1836 // finished but before server called setSaveUiState() - need to cancel the new 1837 // session to avoid further inconsistent behavior. 1838 Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown 1839 + ") called on existing session " + mSessionId + "; cancelling it"); 1840 cancelSessionLocked(); 1841 } 1842 if (shown) { 1843 mSessionId = sessionId; 1844 mState = STATE_SHOWING_SAVE_UI; 1845 } else { 1846 mSessionId = NO_SESSION; 1847 mState = STATE_UNKNOWN; 1848 } 1849 } 1850 } 1851 1852 /** 1853 * Marks the state of the session as finished. 1854 * 1855 * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} 1856 * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or 1857 * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill 1858 * requests for the activity). 1859 */ 1860 private void setSessionFinished(int newState) { 1861 synchronized (mLock) { 1862 if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState); 1863 resetSessionLocked(/* resetEnteredIds= */ false); 1864 mState = newState; 1865 } 1866 } 1867 1868 private void requestHideFillUi(AutofillId id) { 1869 final View anchor = findView(id); 1870 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor); 1871 if (anchor == null) { 1872 return; 1873 } 1874 requestHideFillUi(id, anchor); 1875 } 1876 1877 private void requestHideFillUi(AutofillId id, View anchor) { 1878 1879 AutofillCallback callback = null; 1880 synchronized (mLock) { 1881 // We do not check the session id for two reasons: 1882 // 1. If local and remote session id are off sync the UI would be stuck shown 1883 // 2. There is a race between the user state being destroyed due the fill 1884 // service being uninstalled and the UI being dismissed. 1885 AutofillClient client = getClient(); 1886 if (client != null) { 1887 if (client.autofillClientRequestHideFillUi() && mCallback != null) { 1888 callback = mCallback; 1889 } 1890 } 1891 } 1892 1893 if (callback != null) { 1894 if (id.isVirtual()) { 1895 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1896 AutofillCallback.EVENT_INPUT_HIDDEN); 1897 } else { 1898 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); 1899 } 1900 } 1901 } 1902 1903 private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 1904 if (sVerbose) { 1905 Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id 1906 + ", sessionFinishedState=" + sessionFinishedState); 1907 } 1908 final View anchor = findView(id); 1909 if (anchor == null) { 1910 return; 1911 } 1912 1913 AutofillCallback callback = null; 1914 synchronized (mLock) { 1915 if (mSessionId == sessionId && getClient() != null) { 1916 callback = mCallback; 1917 } 1918 } 1919 1920 if (callback != null) { 1921 if (id.isVirtual()) { 1922 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1923 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1924 } else { 1925 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1926 } 1927 } 1928 1929 if (sessionFinishedState != 0) { 1930 // Callback call was "hijacked" to also update the session state. 1931 setSessionFinished(sessionFinishedState); 1932 } 1933 } 1934 1935 /** 1936 * Find a single view by its id. 1937 * 1938 * @param autofillId The autofill id of the view 1939 * 1940 * @return The view or {@code null} if view was not found 1941 */ 1942 private View findView(@NonNull AutofillId autofillId) { 1943 final AutofillClient client = getClient(); 1944 if (client != null) { 1945 return client.autofillClientFindViewByAutofillIdTraversal(autofillId); 1946 } 1947 return null; 1948 } 1949 1950 /** @hide */ 1951 public boolean hasAutofillFeature() { 1952 return mService != null; 1953 } 1954 1955 /** @hide */ 1956 public void onPendingSaveUi(int operation, IBinder token) { 1957 if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 1958 1959 synchronized (mLock) { 1960 try { 1961 mService.onPendingSaveUi(operation, token); 1962 } catch (RemoteException e) { 1963 e.rethrowFromSystemServer(); 1964 } 1965 } 1966 } 1967 1968 /** @hide */ 1969 public void dump(String outerPrefix, PrintWriter pw) { 1970 pw.print(outerPrefix); pw.println("AutofillManager:"); 1971 final String pfx = outerPrefix + " "; 1972 pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); 1973 pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); 1974 pw.print(pfx); pw.print("context: "); pw.println(mContext); 1975 pw.print(pfx); pw.print("client: "); pw.println(getClient()); 1976 pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); 1977 pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); 1978 pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); 1979 pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); 1980 pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); 1981 pw.print(pfx); pw.print("tracked views: "); 1982 if (mTrackedViews == null) { 1983 pw.println("null"); 1984 } else { 1985 final String pfx2 = pfx + " "; 1986 pw.println(); 1987 pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds); 1988 pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); 1989 } 1990 pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); 1991 pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds); 1992 pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); 1993 pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); 1994 pw.print(pfx); pw.print("compat mode enabled: "); pw.println( 1995 isCompatibilityModeEnabledLocked()); 1996 pw.print(pfx); pw.print("debug: "); pw.print(sDebug); 1997 pw.print(" verbose: "); pw.println(sVerbose); 1998 } 1999 2000 @GuardedBy("mLock") 2001 private String getStateAsStringLocked() { 2002 switch (mState) { 2003 case STATE_UNKNOWN: 2004 return "STATE_UNKNOWN"; 2005 case STATE_ACTIVE: 2006 return "STATE_ACTIVE"; 2007 case STATE_FINISHED: 2008 return "STATE_FINISHED"; 2009 case STATE_SHOWING_SAVE_UI: 2010 return "STATE_SHOWING_SAVE_UI"; 2011 case STATE_DISABLED_BY_SERVICE: 2012 return "STATE_DISABLED_BY_SERVICE"; 2013 default: 2014 return "INVALID:" + mState; 2015 } 2016 } 2017 2018 @GuardedBy("mLock") 2019 private boolean isActiveLocked() { 2020 return mState == STATE_ACTIVE; 2021 } 2022 2023 @GuardedBy("mLock") 2024 private boolean isDisabledByServiceLocked() { 2025 return mState == STATE_DISABLED_BY_SERVICE; 2026 } 2027 2028 @GuardedBy("mLock") 2029 private boolean isFinishedLocked() { 2030 return mState == STATE_FINISHED; 2031 } 2032 2033 private void post(Runnable runnable) { 2034 final AutofillClient client = getClient(); 2035 if (client == null) { 2036 if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); 2037 return; 2038 } 2039 client.autofillClientRunOnUiThread(runnable); 2040 } 2041 2042 /** 2043 * Implementation of the accessibility based compatibility. 2044 */ 2045 private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy { 2046 @GuardedBy("mLock") 2047 private final Rect mFocusedBounds = new Rect(); 2048 @GuardedBy("mLock") 2049 private final Rect mTempBounds = new Rect(); 2050 2051 @GuardedBy("mLock") 2052 private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2053 @GuardedBy("mLock") 2054 private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2055 2056 // Need to report a fake service in case a11y clients check the service list 2057 @NonNull 2058 @GuardedBy("mLock") 2059 AccessibilityServiceInfo mCompatServiceInfo; 2060 2061 CompatibilityBridge() { 2062 final AccessibilityManager am = AccessibilityManager.getInstance(mContext); 2063 am.setAccessibilityPolicy(this); 2064 } 2065 2066 private AccessibilityServiceInfo getCompatServiceInfo() { 2067 synchronized (mLock) { 2068 if (mCompatServiceInfo != null) { 2069 return mCompatServiceInfo; 2070 } 2071 final Intent intent = new Intent(); 2072 intent.setComponent(new ComponentName("android", 2073 "com.android.server.autofill.AutofillCompatAccessibilityService")); 2074 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService( 2075 intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 2076 try { 2077 mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); 2078 } catch (XmlPullParserException | IOException e) { 2079 Log.e(TAG, "Cannot find compat autofill service:" + intent); 2080 throw new IllegalStateException("Cannot find compat autofill service"); 2081 } 2082 return mCompatServiceInfo; 2083 } 2084 } 2085 2086 @Override 2087 public boolean isEnabled(boolean accessibilityEnabled) { 2088 return true; 2089 } 2090 2091 @Override 2092 public int getRelevantEventTypes(int relevantEventTypes) { 2093 return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED 2094 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED 2095 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 2096 } 2097 2098 @Override 2099 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( 2100 List<AccessibilityServiceInfo> installedServices) { 2101 if (installedServices == null) { 2102 installedServices = new ArrayList<>(); 2103 } 2104 installedServices.add(getCompatServiceInfo()); 2105 return installedServices; 2106 } 2107 2108 @Override 2109 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 2110 int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) { 2111 if (enabledService == null) { 2112 enabledService = new ArrayList<>(); 2113 } 2114 enabledService.add(getCompatServiceInfo()); 2115 return enabledService; 2116 } 2117 2118 @Override 2119 public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event, 2120 boolean accessibilityEnabled, int relevantEventTypes) { 2121 switch (event.getEventType()) { 2122 case AccessibilityEvent.TYPE_VIEW_FOCUSED: { 2123 synchronized (mLock) { 2124 if (mFocusedWindowId == event.getWindowId() 2125 && mFocusedNodeId == event.getSourceNodeId()) { 2126 return event; 2127 } 2128 if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID 2129 && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) { 2130 notifyViewExited(mFocusedWindowId, mFocusedNodeId); 2131 mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2132 mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2133 mFocusedBounds.set(0, 0, 0, 0); 2134 } 2135 final int windowId = event.getWindowId(); 2136 final long nodeId = event.getSourceNodeId(); 2137 if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) { 2138 mFocusedWindowId = windowId; 2139 mFocusedNodeId = nodeId; 2140 } 2141 } 2142 } break; 2143 2144 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: { 2145 synchronized (mLock) { 2146 if (mFocusedWindowId == event.getWindowId() 2147 && mFocusedNodeId == event.getSourceNodeId()) { 2148 notifyValueChanged(event.getWindowId(), event.getSourceNodeId()); 2149 } 2150 } 2151 } break; 2152 2153 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { 2154 final AutofillClient client = getClient(); 2155 if (client != null) { 2156 synchronized (mLock) { 2157 if (client.autofillClientIsFillUiShowing()) { 2158 notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds); 2159 } 2160 updateTrackedViewsLocked(); 2161 } 2162 } 2163 } break; 2164 } 2165 2166 return accessibilityEnabled ? event : null; 2167 } 2168 2169 private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) { 2170 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2171 if (!isVirtualNode(virtualId)) { 2172 return false; 2173 } 2174 final View view = findViewByAccessibilityId(windowId, nodeId); 2175 if (view == null) { 2176 return false; 2177 } 2178 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2179 if (node == null) { 2180 return false; 2181 } 2182 if (!node.isEditable()) { 2183 return false; 2184 } 2185 final Rect newBounds = mTempBounds; 2186 node.getBoundsInScreen(newBounds); 2187 if (newBounds.equals(focusedBounds)) { 2188 return false; 2189 } 2190 focusedBounds.set(newBounds); 2191 AutofillManager.this.notifyViewEntered(view, virtualId, newBounds); 2192 return true; 2193 } 2194 2195 private void notifyViewExited(int windowId, long nodeId) { 2196 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2197 if (!isVirtualNode(virtualId)) { 2198 return; 2199 } 2200 final View view = findViewByAccessibilityId(windowId, nodeId); 2201 if (view == null) { 2202 return; 2203 } 2204 AutofillManager.this.notifyViewExited(view, virtualId); 2205 } 2206 2207 private void notifyValueChanged(int windowId, long nodeId) { 2208 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2209 if (!isVirtualNode(virtualId)) { 2210 return; 2211 } 2212 final View view = findViewByAccessibilityId(windowId, nodeId); 2213 if (view == null) { 2214 return; 2215 } 2216 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2217 if (node == null) { 2218 return; 2219 } 2220 AutofillManager.this.notifyValueChanged(view, virtualId, 2221 AutofillValue.forText(node.getText())); 2222 } 2223 2224 @GuardedBy("mLock") 2225 private void updateTrackedViewsLocked() { 2226 if (mTrackedViews != null) { 2227 mTrackedViews.onVisibleForAutofillChangedLocked(); 2228 } 2229 } 2230 2231 private View findViewByAccessibilityId(int windowId, long nodeId) { 2232 final AutofillClient client = getClient(); 2233 if (client == null) { 2234 return null; 2235 } 2236 final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId); 2237 return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId); 2238 } 2239 2240 private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) { 2241 final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 2242 if (provider == null) { 2243 return null; 2244 } 2245 return provider.createAccessibilityNodeInfo(virtualId); 2246 } 2247 2248 private boolean isVirtualNode(int nodeId) { 2249 return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID 2250 && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 2251 } 2252 } 2253 2254 /** 2255 * View tracking information. Once all tracked views become invisible the session is finished. 2256 */ 2257 private class TrackedViews { 2258 /** Visible tracked views */ 2259 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds; 2260 2261 /** Invisible tracked views */ 2262 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds; 2263 2264 /** 2265 * Check if set is null or value is in set. 2266 * 2267 * @param set The set or null (== empty set) 2268 * @param value The value that might be in the set 2269 * 2270 * @return {@code true} iff set is not empty and value is in set 2271 */ 2272 // TODO: move to Helper as static method 2273 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { 2274 return set != null && set.contains(value); 2275 } 2276 2277 /** 2278 * Add a value to a set. If set is null, create a new set. 2279 * 2280 * @param set The set or null (== empty set) 2281 * @param valueToAdd The value to add 2282 * 2283 * @return The set including the new value. If set was {@code null}, a set containing only 2284 * the new value. 2285 */ 2286 // TODO: move to Helper as static method 2287 @NonNull 2288 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { 2289 if (set == null) { 2290 set = new ArraySet<>(1); 2291 } 2292 2293 set.add(valueToAdd); 2294 2295 return set; 2296 } 2297 2298 /** 2299 * Remove a value from a set. 2300 * 2301 * @param set The set or null (== empty set) 2302 * @param valueToRemove The value to remove 2303 * 2304 * @return The set without the removed value. {@code null} if set was null, or is empty 2305 * after removal. 2306 */ 2307 // TODO: move to Helper as static method 2308 @Nullable 2309 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { 2310 if (set == null) { 2311 return null; 2312 } 2313 2314 set.remove(valueToRemove); 2315 2316 if (set.isEmpty()) { 2317 return null; 2318 } 2319 2320 return set; 2321 } 2322 2323 /** 2324 * Set the tracked views. 2325 * 2326 * @param trackedIds The views to be tracked 2327 */ 2328 TrackedViews(@Nullable AutofillId[] trackedIds) { 2329 final AutofillClient client = getClient(); 2330 if (!ArrayUtils.isEmpty(trackedIds) && client != null) { 2331 final boolean[] isVisible; 2332 2333 if (client.autofillClientIsVisibleForAutofill()) { 2334 isVisible = client.autofillClientGetViewVisibility(trackedIds); 2335 } else { 2336 // All false 2337 isVisible = new boolean[trackedIds.length]; 2338 } 2339 2340 final int numIds = trackedIds.length; 2341 for (int i = 0; i < numIds; i++) { 2342 final AutofillId id = trackedIds[i]; 2343 2344 if (isVisible[i]) { 2345 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2346 } else { 2347 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2348 } 2349 } 2350 } 2351 2352 if (sVerbose) { 2353 Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): " 2354 + " mVisibleTrackedIds=" + mVisibleTrackedIds 2355 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); 2356 } 2357 2358 if (mVisibleTrackedIds == null) { 2359 finishSessionLocked(); 2360 } 2361 } 2362 2363 /** 2364 * Called when a {@link View view's} visibility changes. 2365 * 2366 * @param id the id of the view/virtual view whose visibility changed. 2367 * @param isVisible visible if the view is visible in the view hierarchy. 2368 */ 2369 @GuardedBy("mLock") 2370 void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { 2371 if (sDebug) { 2372 Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible=" 2373 + isVisible); 2374 } 2375 2376 if (isClientVisibleForAutofillLocked()) { 2377 if (isVisible) { 2378 if (isInSet(mInvisibleTrackedIds, id)) { 2379 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); 2380 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2381 } 2382 } else { 2383 if (isInSet(mVisibleTrackedIds, id)) { 2384 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); 2385 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2386 } 2387 } 2388 } 2389 2390 if (mVisibleTrackedIds == null) { 2391 if (sVerbose) { 2392 Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds); 2393 } 2394 finishSessionLocked(); 2395 } 2396 } 2397 2398 /** 2399 * Called once the client becomes visible. 2400 * 2401 * @see AutofillClient#autofillClientIsVisibleForAutofill() 2402 */ 2403 @GuardedBy("mLock") 2404 void onVisibleForAutofillChangedLocked() { 2405 // The visibility of the views might have changed while the client was not be visible, 2406 // hence update the visibility state for all views. 2407 AutofillClient client = getClient(); 2408 ArraySet<AutofillId> updatedVisibleTrackedIds = null; 2409 ArraySet<AutofillId> updatedInvisibleTrackedIds = null; 2410 if (client != null) { 2411 if (mInvisibleTrackedIds != null) { 2412 final ArrayList<AutofillId> orderedInvisibleIds = 2413 new ArrayList<>(mInvisibleTrackedIds); 2414 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2415 Helper.toArray(orderedInvisibleIds)); 2416 2417 final int numInvisibleTrackedIds = orderedInvisibleIds.size(); 2418 for (int i = 0; i < numInvisibleTrackedIds; i++) { 2419 final AutofillId id = orderedInvisibleIds.get(i); 2420 if (isVisible[i]) { 2421 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2422 2423 if (sDebug) { 2424 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); 2425 } 2426 } else { 2427 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2428 } 2429 } 2430 } 2431 2432 if (mVisibleTrackedIds != null) { 2433 final ArrayList<AutofillId> orderedVisibleIds = 2434 new ArrayList<>(mVisibleTrackedIds); 2435 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2436 Helper.toArray(orderedVisibleIds)); 2437 2438 final int numVisibleTrackedIds = orderedVisibleIds.size(); 2439 for (int i = 0; i < numVisibleTrackedIds; i++) { 2440 final AutofillId id = orderedVisibleIds.get(i); 2441 2442 if (isVisible[i]) { 2443 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2444 } else { 2445 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2446 2447 if (sDebug) { 2448 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); 2449 } 2450 } 2451 } 2452 } 2453 2454 mInvisibleTrackedIds = updatedInvisibleTrackedIds; 2455 mVisibleTrackedIds = updatedVisibleTrackedIds; 2456 } 2457 2458 if (mVisibleTrackedIds == null) { 2459 finishSessionLocked(); 2460 } 2461 } 2462 } 2463 2464 /** 2465 * Callback for autofill related events. 2466 * 2467 * <p>Typically used for applications that display their own "auto-complete" views, so they can 2468 * enable / disable such views when the autofill UI is shown / hidden. 2469 */ 2470 public abstract static class AutofillCallback { 2471 2472 /** @hide */ 2473 @IntDef(prefix = { "EVENT_INPUT_" }, value = { 2474 EVENT_INPUT_SHOWN, 2475 EVENT_INPUT_HIDDEN, 2476 EVENT_INPUT_UNAVAILABLE 2477 }) 2478 @Retention(RetentionPolicy.SOURCE) 2479 public @interface AutofillEventType {} 2480 2481 /** 2482 * The autofill input UI associated with the view was shown. 2483 * 2484 * <p>If the view provides its own auto-complete UI and its currently shown, it 2485 * should be hidden upon receiving this event. 2486 */ 2487 public static final int EVENT_INPUT_SHOWN = 1; 2488 2489 /** 2490 * The autofill input UI associated with the view was hidden. 2491 * 2492 * <p>If the view provides its own auto-complete UI that was hidden upon a 2493 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. 2494 */ 2495 public static final int EVENT_INPUT_HIDDEN = 2; 2496 2497 /** 2498 * The autofill input UI associated with the view isn't shown because 2499 * autofill is not available. 2500 * 2501 * <p>If the view provides its own auto-complete UI but was not displaying it 2502 * to avoid flickering, it could shown it upon receiving this event. 2503 */ 2504 public static final int EVENT_INPUT_UNAVAILABLE = 3; 2505 2506 /** 2507 * Called after a change in the autofill state associated with a view. 2508 * 2509 * @param view view associated with the change. 2510 * 2511 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2512 */ 2513 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { 2514 } 2515 2516 /** 2517 * Called after a change in the autofill state associated with a virtual view. 2518 * 2519 * @param view parent view associated with the change. 2520 * @param virtualId id identifying the virtual child inside the parent view. 2521 * 2522 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2523 */ 2524 public void onAutofillEvent(@NonNull View view, int virtualId, 2525 @AutofillEventType int event) { 2526 } 2527 } 2528 2529 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { 2530 private final WeakReference<AutofillManager> mAfm; 2531 2532 AutofillManagerClient(AutofillManager autofillManager) { 2533 mAfm = new WeakReference<>(autofillManager); 2534 } 2535 2536 @Override 2537 public void setState(int flags) { 2538 final AutofillManager afm = mAfm.get(); 2539 if (afm != null) { 2540 afm.post(() -> afm.setState(flags)); 2541 } 2542 } 2543 2544 @Override 2545 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 2546 final AutofillManager afm = mAfm.get(); 2547 if (afm != null) { 2548 afm.post(() -> afm.autofill(sessionId, ids, values)); 2549 } 2550 } 2551 2552 @Override 2553 public void authenticate(int sessionId, int authenticationId, IntentSender intent, 2554 Intent fillInIntent) { 2555 final AutofillManager afm = mAfm.get(); 2556 if (afm != null) { 2557 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent)); 2558 } 2559 } 2560 2561 @Override 2562 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 2563 Rect anchorBounds, IAutofillWindowPresenter presenter) { 2564 final AutofillManager afm = mAfm.get(); 2565 if (afm != null) { 2566 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 2567 presenter)); 2568 } 2569 } 2570 2571 @Override 2572 public void requestHideFillUi(int sessionId, AutofillId id) { 2573 final AutofillManager afm = mAfm.get(); 2574 if (afm != null) { 2575 afm.post(() -> afm.requestHideFillUi(id)); 2576 } 2577 } 2578 2579 @Override 2580 public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 2581 final AutofillManager afm = mAfm.get(); 2582 if (afm != null) { 2583 afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); 2584 } 2585 } 2586 2587 @Override 2588 public void startIntentSender(IntentSender intentSender, Intent intent) { 2589 final AutofillManager afm = mAfm.get(); 2590 if (afm != null) { 2591 afm.post(() -> { 2592 try { 2593 afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); 2594 } catch (IntentSender.SendIntentException e) { 2595 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); 2596 } 2597 }); 2598 } 2599 } 2600 2601 @Override 2602 public void setTrackedViews(int sessionId, AutofillId[] ids, 2603 boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, 2604 AutofillId saveTriggerId) { 2605 final AutofillManager afm = mAfm.get(); 2606 if (afm != null) { 2607 afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, 2608 saveOnFinish, fillableIds, saveTriggerId)); 2609 } 2610 } 2611 2612 @Override 2613 public void setSaveUiState(int sessionId, boolean shown) { 2614 final AutofillManager afm = mAfm.get(); 2615 if (afm != null) { 2616 afm.post(() -> afm.setSaveUiState(sessionId, shown)); 2617 } 2618 } 2619 2620 @Override 2621 public void setSessionFinished(int newState) { 2622 final AutofillManager afm = mAfm.get(); 2623 if (afm != null) { 2624 afm.post(() -> afm.setSessionFinished(newState)); 2625 } 2626 } 2627 } 2628} 2629