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