AutofillManager.java revision 3f24e69dbed74fd7724c0a4714ce612f1cb5bc5c
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 if (mSessionId != NO_SESSION) { 1412 mState = STATE_ACTIVE; 1413 } 1414 client.autofillClientResetableStateAvailable(); 1415 } catch (RemoteException e) { 1416 throw e.rethrowFromSystemServer(); 1417 } 1418 } 1419 1420 @GuardedBy("mLock") 1421 private void finishSessionLocked() { 1422 if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); 1423 1424 if (!isActiveLocked()) return; 1425 1426 try { 1427 mService.finishSession(mSessionId, mContext.getUserId()); 1428 } catch (RemoteException e) { 1429 throw e.rethrowFromSystemServer(); 1430 } 1431 1432 resetSessionLocked(); 1433 } 1434 1435 @GuardedBy("mLock") 1436 private void cancelSessionLocked() { 1437 if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); 1438 1439 if (!isActiveLocked()) return; 1440 1441 try { 1442 mService.cancelSession(mSessionId, mContext.getUserId()); 1443 } catch (RemoteException e) { 1444 throw e.rethrowFromSystemServer(); 1445 } 1446 1447 resetSessionLocked(); 1448 } 1449 1450 @GuardedBy("mLock") 1451 private void resetSessionLocked() { 1452 mSessionId = NO_SESSION; 1453 mState = STATE_UNKNOWN; 1454 mTrackedViews = null; 1455 mFillableIds = null; 1456 mSaveTriggerId = null; 1457 } 1458 1459 @GuardedBy("mLock") 1460 private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, 1461 int flags) { 1462 if (sVerbose && action != ACTION_VIEW_EXITED) { 1463 Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds 1464 + ", value=" + value + ", action=" + action + ", flags=" + flags); 1465 } 1466 boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0; 1467 1468 try { 1469 if (restartIfNecessary) { 1470 final AutofillClient client = getClient(); 1471 if (client == null) return; // NOTE: getClient() already logd it.. 1472 1473 final int newId = mService.updateOrRestartSession( 1474 client.autofillClientGetActivityToken(), 1475 mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), 1476 mCallback != null, flags, client.autofillClientGetComponentName(), 1477 mSessionId, action); 1478 if (newId != mSessionId) { 1479 if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); 1480 mSessionId = newId; 1481 mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; 1482 client.autofillClientResetableStateAvailable(); 1483 } 1484 } else { 1485 mService.updateSession(mSessionId, id, bounds, value, action, flags, 1486 mContext.getUserId()); 1487 } 1488 1489 } catch (RemoteException e) { 1490 throw e.rethrowFromSystemServer(); 1491 } 1492 } 1493 1494 @GuardedBy("mLock") 1495 private void ensureServiceClientAddedIfNeededLocked() { 1496 if (getClient() == null) { 1497 return; 1498 } 1499 1500 if (mServiceClient == null) { 1501 mServiceClient = new AutofillManagerClient(this); 1502 try { 1503 final int userId = mContext.getUserId(); 1504 final int flags = mService.addClient(mServiceClient, userId); 1505 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; 1506 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; 1507 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; 1508 final IAutoFillManager service = mService; 1509 final IAutoFillManagerClient serviceClient = mServiceClient; 1510 mServiceClientCleaner = Cleaner.create(this, () -> { 1511 try { 1512 service.removeClient(serviceClient, userId); 1513 } catch (RemoteException e) { 1514 } 1515 }); 1516 } catch (RemoteException e) { 1517 throw e.rethrowFromSystemServer(); 1518 } 1519 } 1520 } 1521 1522 /** 1523 * Registers a {@link AutofillCallback} to receive autofill events. 1524 * 1525 * @param callback callback to receive events. 1526 */ 1527 public void registerCallback(@Nullable AutofillCallback callback) { 1528 if (!hasAutofillFeature()) { 1529 return; 1530 } 1531 synchronized (mLock) { 1532 if (callback == null) return; 1533 1534 final boolean hadCallback = mCallback != null; 1535 mCallback = callback; 1536 1537 if (!hadCallback) { 1538 try { 1539 mService.setHasCallback(mSessionId, mContext.getUserId(), true); 1540 } catch (RemoteException e) { 1541 throw e.rethrowFromSystemServer(); 1542 } 1543 } 1544 } 1545 } 1546 1547 /** 1548 * Unregisters a {@link AutofillCallback} to receive autofill events. 1549 * 1550 * @param callback callback to stop receiving events. 1551 */ 1552 public void unregisterCallback(@Nullable AutofillCallback callback) { 1553 if (!hasAutofillFeature()) { 1554 return; 1555 } 1556 synchronized (mLock) { 1557 if (callback == null || mCallback == null || callback != mCallback) return; 1558 1559 mCallback = null; 1560 1561 try { 1562 mService.setHasCallback(mSessionId, mContext.getUserId(), false); 1563 } catch (RemoteException e) { 1564 throw e.rethrowFromSystemServer(); 1565 } 1566 } 1567 } 1568 1569 private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 1570 Rect anchorBounds, IAutofillWindowPresenter presenter) { 1571 final View anchor = findView(id); 1572 if (anchor == null) { 1573 return; 1574 } 1575 1576 AutofillCallback callback = null; 1577 synchronized (mLock) { 1578 if (mSessionId == sessionId) { 1579 AutofillClient client = getClient(); 1580 1581 if (client != null) { 1582 if (client.autofillClientRequestShowFillUi(anchor, width, height, 1583 anchorBounds, presenter) && mCallback != null) { 1584 callback = mCallback; 1585 } 1586 } 1587 } 1588 } 1589 1590 if (callback != null) { 1591 if (id.isVirtual()) { 1592 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1593 AutofillCallback.EVENT_INPUT_SHOWN); 1594 } else { 1595 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); 1596 } 1597 } 1598 } 1599 1600 private void authenticate(int sessionId, int authenticationId, IntentSender intent, 1601 Intent fillInIntent) { 1602 synchronized (mLock) { 1603 if (sessionId == mSessionId) { 1604 final AutofillClient client = getClient(); 1605 if (client != null) { 1606 // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() 1607 // before onAuthenticationResult() 1608 mOnInvisibleCalled = false; 1609 client.autofillClientAuthenticate(authenticationId, intent, fillInIntent); 1610 } 1611 } 1612 } 1613 } 1614 1615 /** @hide */ 1616 public static final int SET_STATE_FLAG_ENABLED = 0x01; 1617 /** @hide */ 1618 public static final int SET_STATE_FLAG_RESET_SESSION = 0x02; 1619 /** @hide */ 1620 public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04; 1621 /** @hide */ 1622 public static final int SET_STATE_FLAG_DEBUG = 0x08; 1623 /** @hide */ 1624 public static final int SET_STATE_FLAG_VERBOSE = 0x10; 1625 1626 private void setState(int flags) { 1627 if (sVerbose) Log.v(TAG, "setState(" + flags + ")"); 1628 synchronized (mLock) { 1629 mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; 1630 if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { 1631 // Reset the session state 1632 resetSessionLocked(); 1633 } 1634 if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { 1635 // Reset connection to system 1636 mServiceClient = null; 1637 if (mServiceClientCleaner != null) { 1638 mServiceClientCleaner.clean(); 1639 mServiceClientCleaner = null; 1640 } 1641 } 1642 } 1643 sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; 1644 sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; 1645 } 1646 1647 /** 1648 * Sets a view as autofilled if the current value is the {code targetValue}. 1649 * 1650 * @param view The view that is to be autofilled 1651 * @param targetValue The value we want to fill into view 1652 */ 1653 private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { 1654 AutofillValue currentValue = view.getAutofillValue(); 1655 if (Objects.equals(currentValue, targetValue)) { 1656 synchronized (mLock) { 1657 if (mLastAutofilledData == null) { 1658 mLastAutofilledData = new ParcelableMap(1); 1659 } 1660 mLastAutofilledData.put(getAutofillId(view), targetValue); 1661 } 1662 view.setAutofilled(true); 1663 } 1664 } 1665 1666 private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 1667 synchronized (mLock) { 1668 if (sessionId != mSessionId) { 1669 return; 1670 } 1671 1672 final AutofillClient client = getClient(); 1673 if (client == null) { 1674 return; 1675 } 1676 1677 final int itemCount = ids.size(); 1678 int numApplied = 0; 1679 ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; 1680 final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( 1681 Helper.toArray(ids)); 1682 1683 for (int i = 0; i < itemCount; i++) { 1684 final AutofillId id = ids.get(i); 1685 final AutofillValue value = values.get(i); 1686 final int viewId = id.getViewId(); 1687 final View view = views[i]; 1688 if (view == null) { 1689 Log.w(TAG, "autofill(): no View with id " + viewId); 1690 continue; 1691 } 1692 if (id.isVirtual()) { 1693 if (virtualValues == null) { 1694 // Most likely there will be just one view with virtual children. 1695 virtualValues = new ArrayMap<>(1); 1696 } 1697 SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); 1698 if (valuesByParent == null) { 1699 // We don't know the size yet, but usually it will be just a few fields... 1700 valuesByParent = new SparseArray<>(5); 1701 virtualValues.put(view, valuesByParent); 1702 } 1703 valuesByParent.put(id.getVirtualChildId(), value); 1704 } else { 1705 // Mark the view as to be autofilled with 'value' 1706 if (mLastAutofilledData == null) { 1707 mLastAutofilledData = new ParcelableMap(itemCount - i); 1708 } 1709 mLastAutofilledData.put(id, value); 1710 1711 view.autofill(value); 1712 1713 // Set as autofilled if the values match now, e.g. when the value was updated 1714 // synchronously. 1715 // If autofill happens async, the view is set to autofilled in 1716 // notifyValueChanged. 1717 setAutofilledIfValuesIs(view, value); 1718 1719 numApplied++; 1720 } 1721 } 1722 1723 if (virtualValues != null) { 1724 for (int i = 0; i < virtualValues.size(); i++) { 1725 final View parent = virtualValues.keyAt(i); 1726 final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); 1727 parent.autofill(childrenValues); 1728 numApplied += childrenValues.size(); 1729 } 1730 } 1731 1732 final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED) 1733 .setPackageName(mContext.getPackageName()) 1734 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) 1735 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); 1736 mMetricsLogger.write(log); 1737 } 1738 } 1739 1740 /** 1741 * Set the tracked views. 1742 * 1743 * @param trackedIds The views to be tracked. 1744 * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. 1745 * @param saveOnFinish Finish the session once the activity is finished. 1746 * @param fillableIds Views that might anchor FillUI. 1747 * @param saveTriggerId View that when clicked triggers commit(). 1748 */ 1749 private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, 1750 boolean saveOnAllViewsInvisible, boolean saveOnFinish, 1751 @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { 1752 synchronized (mLock) { 1753 if (mEnabled && mSessionId == sessionId) { 1754 if (saveOnAllViewsInvisible) { 1755 mTrackedViews = new TrackedViews(trackedIds); 1756 } else { 1757 mTrackedViews = null; 1758 } 1759 mSaveOnFinish = saveOnFinish; 1760 if (fillableIds != null) { 1761 if (mFillableIds == null) { 1762 mFillableIds = new ArraySet<>(fillableIds.length); 1763 } 1764 for (AutofillId id : fillableIds) { 1765 mFillableIds.add(id); 1766 } 1767 if (sVerbose) { 1768 Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds 1769 + ", mFillableIds" + mFillableIds); 1770 } 1771 } 1772 1773 if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { 1774 // Turn off trigger on previous view id. 1775 setNotifyOnClickLocked(mSaveTriggerId, false); 1776 } 1777 1778 if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { 1779 // Turn on trigger on new view id. 1780 mSaveTriggerId = saveTriggerId; 1781 setNotifyOnClickLocked(mSaveTriggerId, true); 1782 } 1783 } 1784 } 1785 } 1786 1787 private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { 1788 final View view = findView(id); 1789 if (view == null) { 1790 Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); 1791 return; 1792 } 1793 view.setNotifyAutofillManagerOnClick(notify); 1794 } 1795 1796 private void setSaveUiState(int sessionId, boolean shown) { 1797 if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); 1798 synchronized (mLock) { 1799 if (mSessionId != NO_SESSION) { 1800 // Race condition: app triggered a new session after the previous session was 1801 // finished but before server called setSaveUiState() - need to cancel the new 1802 // session to avoid further inconsistent behavior. 1803 Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown 1804 + ") called on existing session " + mSessionId + "; cancelling it"); 1805 cancelSessionLocked(); 1806 } 1807 if (shown) { 1808 mSessionId = sessionId; 1809 mState = STATE_SHOWING_SAVE_UI; 1810 } else { 1811 mSessionId = NO_SESSION; 1812 mState = STATE_UNKNOWN; 1813 } 1814 } 1815 } 1816 1817 /** 1818 * Marks the state of the session as finished. 1819 * 1820 * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} 1821 * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or 1822 * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill 1823 * requests for the activity). 1824 */ 1825 private void setSessionFinished(int newState) { 1826 synchronized (mLock) { 1827 if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState); 1828 resetSessionLocked(); 1829 mState = newState; 1830 } 1831 } 1832 1833 private void requestHideFillUi(AutofillId id) { 1834 final View anchor = findView(id); 1835 if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor); 1836 if (anchor == null) { 1837 return; 1838 } 1839 requestHideFillUi(id, anchor); 1840 } 1841 1842 private void requestHideFillUi(AutofillId id, View anchor) { 1843 1844 AutofillCallback callback = null; 1845 synchronized (mLock) { 1846 // We do not check the session id for two reasons: 1847 // 1. If local and remote session id are off sync the UI would be stuck shown 1848 // 2. There is a race between the user state being destroyed due the fill 1849 // service being uninstalled and the UI being dismissed. 1850 AutofillClient client = getClient(); 1851 if (client != null) { 1852 if (client.autofillClientRequestHideFillUi() && mCallback != null) { 1853 callback = mCallback; 1854 } 1855 } 1856 } 1857 1858 if (callback != null) { 1859 if (id.isVirtual()) { 1860 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1861 AutofillCallback.EVENT_INPUT_HIDDEN); 1862 } else { 1863 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); 1864 } 1865 } 1866 } 1867 1868 private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 1869 if (sVerbose) { 1870 Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id 1871 + ", sessionFinishedState=" + sessionFinishedState); 1872 } 1873 final View anchor = findView(id); 1874 if (anchor == null) { 1875 return; 1876 } 1877 1878 AutofillCallback callback = null; 1879 synchronized (mLock) { 1880 if (mSessionId == sessionId && getClient() != null) { 1881 callback = mCallback; 1882 } 1883 } 1884 1885 if (callback != null) { 1886 if (id.isVirtual()) { 1887 callback.onAutofillEvent(anchor, id.getVirtualChildId(), 1888 AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1889 } else { 1890 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); 1891 } 1892 } 1893 1894 if (sessionFinishedState != 0) { 1895 // Callback call was "hijacked" to also update the session state. 1896 setSessionFinished(sessionFinishedState); 1897 } 1898 } 1899 1900 /** 1901 * Find a single view by its id. 1902 * 1903 * @param autofillId The autofill id of the view 1904 * 1905 * @return The view or {@code null} if view was not found 1906 */ 1907 private View findView(@NonNull AutofillId autofillId) { 1908 final AutofillClient client = getClient(); 1909 if (client != null) { 1910 return client.autofillClientFindViewByAutofillIdTraversal(autofillId); 1911 } 1912 return null; 1913 } 1914 1915 /** @hide */ 1916 public boolean hasAutofillFeature() { 1917 return mService != null; 1918 } 1919 1920 /** @hide */ 1921 public void onPendingSaveUi(int operation, IBinder token) { 1922 if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 1923 1924 synchronized (mLock) { 1925 try { 1926 mService.onPendingSaveUi(operation, token); 1927 } catch (RemoteException e) { 1928 e.rethrowFromSystemServer(); 1929 } 1930 } 1931 } 1932 1933 /** @hide */ 1934 public void dump(String outerPrefix, PrintWriter pw) { 1935 pw.print(outerPrefix); pw.println("AutofillManager:"); 1936 final String pfx = outerPrefix + " "; 1937 pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); 1938 pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); 1939 pw.print(pfx); pw.print("context: "); pw.println(mContext); 1940 pw.print(pfx); pw.print("client: "); pw.println(getClient()); 1941 pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); 1942 pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); 1943 pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); 1944 pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); 1945 pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); 1946 pw.print(pfx); pw.print("tracked views: "); 1947 if (mTrackedViews == null) { 1948 pw.println("null"); 1949 } else { 1950 final String pfx2 = pfx + " "; 1951 pw.println(); 1952 pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds); 1953 pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); 1954 } 1955 pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); 1956 pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); 1957 pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); 1958 pw.print(pfx); pw.print("compat mode enabled: "); pw.println( 1959 isCompatibilityModeEnabledLocked()); 1960 pw.print(pfx); pw.print("debug: "); pw.print(sDebug); 1961 pw.print(" verbose: "); pw.println(sVerbose); 1962 } 1963 1964 @GuardedBy("mLock") 1965 private String getStateAsStringLocked() { 1966 switch (mState) { 1967 case STATE_UNKNOWN: 1968 return "STATE_UNKNOWN"; 1969 case STATE_ACTIVE: 1970 return "STATE_ACTIVE"; 1971 case STATE_FINISHED: 1972 return "STATE_FINISHED"; 1973 case STATE_SHOWING_SAVE_UI: 1974 return "STATE_SHOWING_SAVE_UI"; 1975 case STATE_DISABLED_BY_SERVICE: 1976 return "STATE_DISABLED_BY_SERVICE"; 1977 default: 1978 return "INVALID:" + mState; 1979 } 1980 } 1981 1982 @GuardedBy("mLock") 1983 private boolean isActiveLocked() { 1984 return mState == STATE_ACTIVE; 1985 } 1986 1987 @GuardedBy("mLock") 1988 private boolean isDisabledByServiceLocked() { 1989 return mState == STATE_DISABLED_BY_SERVICE; 1990 } 1991 1992 @GuardedBy("mLock") 1993 private boolean isFinishedLocked() { 1994 return mState == STATE_FINISHED; 1995 } 1996 1997 private void post(Runnable runnable) { 1998 final AutofillClient client = getClient(); 1999 if (client == null) { 2000 if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); 2001 return; 2002 } 2003 client.autofillClientRunOnUiThread(runnable); 2004 } 2005 2006 /** 2007 * Implementation of the accessibility based compatibility. 2008 */ 2009 private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy { 2010 @GuardedBy("mLock") 2011 private final Rect mFocusedBounds = new Rect(); 2012 @GuardedBy("mLock") 2013 private final Rect mTempBounds = new Rect(); 2014 2015 @GuardedBy("mLock") 2016 private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2017 @GuardedBy("mLock") 2018 private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2019 2020 // Need to report a fake service in case a11y clients check the service list 2021 @NonNull 2022 @GuardedBy("mLock") 2023 AccessibilityServiceInfo mCompatServiceInfo; 2024 2025 CompatibilityBridge() { 2026 final AccessibilityManager am = AccessibilityManager.getInstance(mContext); 2027 am.setAccessibilityPolicy(this); 2028 } 2029 2030 private AccessibilityServiceInfo getCompatServiceInfo() { 2031 synchronized (mLock) { 2032 if (mCompatServiceInfo != null) { 2033 return mCompatServiceInfo; 2034 } 2035 final Intent intent = new Intent(); 2036 intent.setComponent(new ComponentName("android", 2037 "com.android.server.autofill.AutofillCompatAccessibilityService")); 2038 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService( 2039 intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 2040 try { 2041 mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); 2042 } catch (XmlPullParserException | IOException e) { 2043 Log.e(TAG, "Cannot find compat autofill service:" + intent); 2044 throw new IllegalStateException("Cannot find compat autofill service"); 2045 } 2046 return mCompatServiceInfo; 2047 } 2048 } 2049 2050 @Override 2051 public boolean isEnabled(boolean accessibilityEnabled) { 2052 return true; 2053 } 2054 2055 @Override 2056 public int getRelevantEventTypes(int relevantEventTypes) { 2057 return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED 2058 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED 2059 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 2060 } 2061 2062 @Override 2063 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( 2064 List<AccessibilityServiceInfo> installedServices) { 2065 if (installedServices == null) { 2066 installedServices = new ArrayList<>(); 2067 } 2068 installedServices.add(getCompatServiceInfo()); 2069 return installedServices; 2070 } 2071 2072 @Override 2073 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 2074 int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) { 2075 if (enabledService == null) { 2076 enabledService = new ArrayList<>(); 2077 } 2078 enabledService.add(getCompatServiceInfo()); 2079 return enabledService; 2080 } 2081 2082 @Override 2083 public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event, 2084 boolean accessibilityEnabled, int relevantEventTypes) { 2085 switch (event.getEventType()) { 2086 case AccessibilityEvent.TYPE_VIEW_FOCUSED: { 2087 synchronized (mLock) { 2088 if (mFocusedWindowId == event.getWindowId() 2089 && mFocusedNodeId == event.getSourceNodeId()) { 2090 return event; 2091 } 2092 if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID 2093 && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) { 2094 notifyViewExited(mFocusedWindowId, mFocusedNodeId); 2095 mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 2096 mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 2097 mFocusedBounds.set(0, 0, 0, 0); 2098 } 2099 final int windowId = event.getWindowId(); 2100 final long nodeId = event.getSourceNodeId(); 2101 if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) { 2102 mFocusedWindowId = windowId; 2103 mFocusedNodeId = nodeId; 2104 } 2105 } 2106 } break; 2107 2108 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: { 2109 synchronized (mLock) { 2110 if (mFocusedWindowId == event.getWindowId() 2111 && mFocusedNodeId == event.getSourceNodeId()) { 2112 notifyValueChanged(event.getWindowId(), event.getSourceNodeId()); 2113 } 2114 } 2115 } break; 2116 2117 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { 2118 final AutofillClient client = getClient(); 2119 if (client != null) { 2120 synchronized (mLock) { 2121 if (client.autofillClientIsFillUiShowing()) { 2122 notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds); 2123 } 2124 updateTrackedViewsLocked(); 2125 } 2126 } 2127 } break; 2128 } 2129 2130 return accessibilityEnabled ? event : null; 2131 } 2132 2133 private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) { 2134 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2135 if (!isVirtualNode(virtualId)) { 2136 return false; 2137 } 2138 final View view = findViewByAccessibilityId(windowId, nodeId); 2139 if (view == null) { 2140 return false; 2141 } 2142 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2143 if (node == null) { 2144 return false; 2145 } 2146 if (!node.isEditable()) { 2147 return false; 2148 } 2149 final Rect newBounds = mTempBounds; 2150 node.getBoundsInScreen(newBounds); 2151 if (newBounds.equals(focusedBounds)) { 2152 return false; 2153 } 2154 focusedBounds.set(newBounds); 2155 AutofillManager.this.notifyViewEntered(view, virtualId, newBounds); 2156 return true; 2157 } 2158 2159 private void notifyViewExited(int windowId, long nodeId) { 2160 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2161 if (!isVirtualNode(virtualId)) { 2162 return; 2163 } 2164 final View view = findViewByAccessibilityId(windowId, nodeId); 2165 if (view == null) { 2166 return; 2167 } 2168 AutofillManager.this.notifyViewExited(view, virtualId); 2169 } 2170 2171 private void notifyValueChanged(int windowId, long nodeId) { 2172 final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); 2173 if (!isVirtualNode(virtualId)) { 2174 return; 2175 } 2176 final View view = findViewByAccessibilityId(windowId, nodeId); 2177 if (view == null) { 2178 return; 2179 } 2180 final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); 2181 if (node == null) { 2182 return; 2183 } 2184 AutofillManager.this.notifyValueChanged(view, virtualId, 2185 AutofillValue.forText(node.getText())); 2186 } 2187 2188 @GuardedBy("mLock") 2189 private void updateTrackedViewsLocked() { 2190 if (mTrackedViews != null) { 2191 mTrackedViews.onVisibleForAutofillChangedLocked(); 2192 } 2193 } 2194 2195 private View findViewByAccessibilityId(int windowId, long nodeId) { 2196 final AutofillClient client = getClient(); 2197 if (client == null) { 2198 return null; 2199 } 2200 final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId); 2201 return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId); 2202 } 2203 2204 private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) { 2205 final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 2206 if (provider == null) { 2207 return null; 2208 } 2209 return provider.createAccessibilityNodeInfo(virtualId); 2210 } 2211 2212 private boolean isVirtualNode(int nodeId) { 2213 return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID 2214 && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 2215 } 2216 } 2217 2218 /** 2219 * View tracking information. Once all tracked views become invisible the session is finished. 2220 */ 2221 private class TrackedViews { 2222 /** Visible tracked views */ 2223 @Nullable private ArraySet<AutofillId> mVisibleTrackedIds; 2224 2225 /** Invisible tracked views */ 2226 @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds; 2227 2228 /** 2229 * Check if set is null or value is in set. 2230 * 2231 * @param set The set or null (== empty set) 2232 * @param value The value that might be in the set 2233 * 2234 * @return {@code true} iff set is not empty and value is in set 2235 */ 2236 // TODO: move to Helper as static method 2237 private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { 2238 return set != null && set.contains(value); 2239 } 2240 2241 /** 2242 * Add a value to a set. If set is null, create a new set. 2243 * 2244 * @param set The set or null (== empty set) 2245 * @param valueToAdd The value to add 2246 * 2247 * @return The set including the new value. If set was {@code null}, a set containing only 2248 * the new value. 2249 */ 2250 // TODO: move to Helper as static method 2251 @NonNull 2252 private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { 2253 if (set == null) { 2254 set = new ArraySet<>(1); 2255 } 2256 2257 set.add(valueToAdd); 2258 2259 return set; 2260 } 2261 2262 /** 2263 * Remove a value from a set. 2264 * 2265 * @param set The set or null (== empty set) 2266 * @param valueToRemove The value to remove 2267 * 2268 * @return The set without the removed value. {@code null} if set was null, or is empty 2269 * after removal. 2270 */ 2271 // TODO: move to Helper as static method 2272 @Nullable 2273 private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { 2274 if (set == null) { 2275 return null; 2276 } 2277 2278 set.remove(valueToRemove); 2279 2280 if (set.isEmpty()) { 2281 return null; 2282 } 2283 2284 return set; 2285 } 2286 2287 /** 2288 * Set the tracked views. 2289 * 2290 * @param trackedIds The views to be tracked 2291 */ 2292 TrackedViews(@Nullable AutofillId[] trackedIds) { 2293 final AutofillClient client = getClient(); 2294 if (!ArrayUtils.isEmpty(trackedIds) && client != null) { 2295 final boolean[] isVisible; 2296 2297 if (client.autofillClientIsVisibleForAutofill()) { 2298 isVisible = client.autofillClientGetViewVisibility(trackedIds); 2299 } else { 2300 // All false 2301 isVisible = new boolean[trackedIds.length]; 2302 } 2303 2304 final int numIds = trackedIds.length; 2305 for (int i = 0; i < numIds; i++) { 2306 final AutofillId id = trackedIds[i]; 2307 2308 if (isVisible[i]) { 2309 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2310 } else { 2311 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2312 } 2313 } 2314 } 2315 2316 if (sVerbose) { 2317 Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): " 2318 + " mVisibleTrackedIds=" + mVisibleTrackedIds 2319 + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); 2320 } 2321 2322 if (mVisibleTrackedIds == null) { 2323 finishSessionLocked(); 2324 } 2325 } 2326 2327 /** 2328 * Called when a {@link View view's} visibility changes. 2329 * 2330 * @param id the id of the view/virtual view whose visibility changed. 2331 * @param isVisible visible if the view is visible in the view hierarchy. 2332 */ 2333 @GuardedBy("mLock") 2334 void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { 2335 if (sDebug) { 2336 Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible=" 2337 + isVisible); 2338 } 2339 2340 if (isClientVisibleForAutofillLocked()) { 2341 if (isVisible) { 2342 if (isInSet(mInvisibleTrackedIds, id)) { 2343 mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); 2344 mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); 2345 } 2346 } else { 2347 if (isInSet(mVisibleTrackedIds, id)) { 2348 mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); 2349 mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); 2350 } 2351 } 2352 } 2353 2354 if (mVisibleTrackedIds == null) { 2355 if (sVerbose) { 2356 Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds); 2357 } 2358 finishSessionLocked(); 2359 } 2360 } 2361 2362 /** 2363 * Called once the client becomes visible. 2364 * 2365 * @see AutofillClient#autofillClientIsVisibleForAutofill() 2366 */ 2367 @GuardedBy("mLock") 2368 void onVisibleForAutofillChangedLocked() { 2369 // The visibility of the views might have changed while the client was not be visible, 2370 // hence update the visibility state for all views. 2371 AutofillClient client = getClient(); 2372 ArraySet<AutofillId> updatedVisibleTrackedIds = null; 2373 ArraySet<AutofillId> updatedInvisibleTrackedIds = null; 2374 if (client != null) { 2375 if (mInvisibleTrackedIds != null) { 2376 final ArrayList<AutofillId> orderedInvisibleIds = 2377 new ArrayList<>(mInvisibleTrackedIds); 2378 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2379 Helper.toArray(orderedInvisibleIds)); 2380 2381 final int numInvisibleTrackedIds = orderedInvisibleIds.size(); 2382 for (int i = 0; i < numInvisibleTrackedIds; i++) { 2383 final AutofillId id = orderedInvisibleIds.get(i); 2384 if (isVisible[i]) { 2385 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2386 2387 if (sDebug) { 2388 Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); 2389 } 2390 } else { 2391 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2392 } 2393 } 2394 } 2395 2396 if (mVisibleTrackedIds != null) { 2397 final ArrayList<AutofillId> orderedVisibleIds = 2398 new ArrayList<>(mVisibleTrackedIds); 2399 final boolean[] isVisible = client.autofillClientGetViewVisibility( 2400 Helper.toArray(orderedVisibleIds)); 2401 2402 final int numVisibleTrackedIds = orderedVisibleIds.size(); 2403 for (int i = 0; i < numVisibleTrackedIds; i++) { 2404 final AutofillId id = orderedVisibleIds.get(i); 2405 2406 if (isVisible[i]) { 2407 updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); 2408 } else { 2409 updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); 2410 2411 if (sDebug) { 2412 Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); 2413 } 2414 } 2415 } 2416 } 2417 2418 mInvisibleTrackedIds = updatedInvisibleTrackedIds; 2419 mVisibleTrackedIds = updatedVisibleTrackedIds; 2420 } 2421 2422 if (mVisibleTrackedIds == null) { 2423 finishSessionLocked(); 2424 } 2425 } 2426 } 2427 2428 /** 2429 * Callback for autofill related events. 2430 * 2431 * <p>Typically used for applications that display their own "auto-complete" views, so they can 2432 * enable / disable such views when the autofill UI is shown / hidden. 2433 */ 2434 public abstract static class AutofillCallback { 2435 2436 /** @hide */ 2437 @IntDef(prefix = { "EVENT_INPUT_" }, value = { 2438 EVENT_INPUT_SHOWN, 2439 EVENT_INPUT_HIDDEN, 2440 EVENT_INPUT_UNAVAILABLE 2441 }) 2442 @Retention(RetentionPolicy.SOURCE) 2443 public @interface AutofillEventType {} 2444 2445 /** 2446 * The autofill input UI associated with the view was shown. 2447 * 2448 * <p>If the view provides its own auto-complete UI and its currently shown, it 2449 * should be hidden upon receiving this event. 2450 */ 2451 public static final int EVENT_INPUT_SHOWN = 1; 2452 2453 /** 2454 * The autofill input UI associated with the view was hidden. 2455 * 2456 * <p>If the view provides its own auto-complete UI that was hidden upon a 2457 * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. 2458 */ 2459 public static final int EVENT_INPUT_HIDDEN = 2; 2460 2461 /** 2462 * The autofill input UI associated with the view isn't shown because 2463 * autofill is not available. 2464 * 2465 * <p>If the view provides its own auto-complete UI but was not displaying it 2466 * to avoid flickering, it could shown it upon receiving this event. 2467 */ 2468 public static final int EVENT_INPUT_UNAVAILABLE = 3; 2469 2470 /** 2471 * Called after a change in the autofill state associated with a view. 2472 * 2473 * @param view view associated with the change. 2474 * 2475 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2476 */ 2477 public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { 2478 } 2479 2480 /** 2481 * Called after a change in the autofill state associated with a virtual view. 2482 * 2483 * @param view parent view associated with the change. 2484 * @param virtualId id identifying the virtual child inside the parent view. 2485 * 2486 * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. 2487 */ 2488 public void onAutofillEvent(@NonNull View view, int virtualId, 2489 @AutofillEventType int event) { 2490 } 2491 } 2492 2493 private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { 2494 private final WeakReference<AutofillManager> mAfm; 2495 2496 AutofillManagerClient(AutofillManager autofillManager) { 2497 mAfm = new WeakReference<>(autofillManager); 2498 } 2499 2500 @Override 2501 public void setState(int flags) { 2502 final AutofillManager afm = mAfm.get(); 2503 if (afm != null) { 2504 afm.post(() -> afm.setState(flags)); 2505 } 2506 } 2507 2508 @Override 2509 public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { 2510 final AutofillManager afm = mAfm.get(); 2511 if (afm != null) { 2512 afm.post(() -> afm.autofill(sessionId, ids, values)); 2513 } 2514 } 2515 2516 @Override 2517 public void authenticate(int sessionId, int authenticationId, IntentSender intent, 2518 Intent fillInIntent) { 2519 final AutofillManager afm = mAfm.get(); 2520 if (afm != null) { 2521 afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent)); 2522 } 2523 } 2524 2525 @Override 2526 public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, 2527 Rect anchorBounds, IAutofillWindowPresenter presenter) { 2528 final AutofillManager afm = mAfm.get(); 2529 if (afm != null) { 2530 afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, 2531 presenter)); 2532 } 2533 } 2534 2535 @Override 2536 public void requestHideFillUi(int sessionId, AutofillId id) { 2537 final AutofillManager afm = mAfm.get(); 2538 if (afm != null) { 2539 afm.post(() -> afm.requestHideFillUi(id)); 2540 } 2541 } 2542 2543 @Override 2544 public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { 2545 final AutofillManager afm = mAfm.get(); 2546 if (afm != null) { 2547 afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); 2548 } 2549 } 2550 2551 @Override 2552 public void startIntentSender(IntentSender intentSender, Intent intent) { 2553 final AutofillManager afm = mAfm.get(); 2554 if (afm != null) { 2555 afm.post(() -> { 2556 try { 2557 afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); 2558 } catch (IntentSender.SendIntentException e) { 2559 Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); 2560 } 2561 }); 2562 } 2563 } 2564 2565 @Override 2566 public void setTrackedViews(int sessionId, AutofillId[] ids, 2567 boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, 2568 AutofillId saveTriggerId) { 2569 final AutofillManager afm = mAfm.get(); 2570 if (afm != null) { 2571 afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, 2572 saveOnFinish, fillableIds, saveTriggerId)); 2573 } 2574 } 2575 2576 @Override 2577 public void setSaveUiState(int sessionId, boolean shown) { 2578 final AutofillManager afm = mAfm.get(); 2579 if (afm != null) { 2580 afm.post(() -> afm.setSaveUiState(sessionId, shown)); 2581 } 2582 } 2583 2584 @Override 2585 public void setSessionFinished(int newState) { 2586 final AutofillManager afm = mAfm.get(); 2587 if (afm != null) { 2588 afm.post(() -> afm.setSessionFinished(newState)); 2589 } 2590 } 2591 } 2592} 2593