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