/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view.autofill;

import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.KeyEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;

The AutofillManager class provides ways for apps and custom views to integrate with the Autofill Framework lifecycle.

To learn about using Autofill in your app, read the Autofill Framework guides.

Autofill lifecycle

The autofill lifecycle starts with the creation of an autofill context associated with an activity context. The autofill context is created when one of the following methods is called for the first time in an activity context, and the current user has an enabled autofill service:

Typically, the context is automatically created when the first view of the activity is focused because View.onFocusChanged() indirectly calls notifyViewEntered(View). App developers can call requestAutofill(View) to explicitly create it (for example, a custom view developer could offer a contextual menu action in a text-field view to let users manually request autofill).

After the context is created, the Android System creates a ViewStructure that represents the view hierarchy by calling View.dispatchProvideAutofillStructure(ViewStructure, int) in the root views of all application windows. By default, dispatchProvideAutofillStructure() results in subsequent calls to View.onProvideAutofillStructure(ViewStructure, int) and View.onProvideAutofillVirtualStructure(ViewStructure, int) for each view in the hierarchy.

The resulting ViewStructure is then passed to the autofill service, which parses it looking for views that can be autofilled. If the service finds such views, it returns a data structure to the Android System containing the following optional info:

  • Datasets used to autofill subsets of views in the activity.
  • Id of views that the service can save their values for future autofilling.

When the service returns datasets, the Android System displays an autofill dataset picker UI associated with the view, when the view is focused on and is part of a dataset. The application can be notified when the UI is shown by registering an AutofillCallback through registerCallback(AutofillCallback). When the user selects a dataset from the UI, all views present in the dataset are autofilled, through calls to View.autofill(AutofillValue) or View.autofill(SparseArray<AutofillValue>).

When the service returns ids of savable views, the Android System keeps track of changes made to these views, so they can be used to determine if the autofill save UI is shown later.

The context is then finished when one of the following occurs:

Finally, after the autofill context is commited (i.e., not cancelled), the Android System shows an autofill save UI if the value of savable views have changed. If the user selects the option to Save, the current value of the views is then sent to the autofill service.

Additional notes

It is safe to call AutofillManager methods from any thread.

/** * <p>The {@link AutofillManager} class provides ways for apps and custom views to * integrate with the Autofill Framework lifecycle. * * <p>To learn about using Autofill in your app, read * the <a href="/guide/topics/text/autofill">Autofill Framework</a> guides. * * <h3 id="autofill-lifecycle">Autofill lifecycle</h3> * * <p>The autofill lifecycle starts with the creation of an autofill context associated with an * activity context. The autofill context is created when one of the following methods is called for * the first time in an activity context, and the current user has an enabled autofill service: * * <ul> * <li>{@link #notifyViewEntered(View)} * <li>{@link #notifyViewEntered(View, int, Rect)} * <li>{@link #requestAutofill(View)} * </ul> * * <p>Typically, the context is automatically created when the first view of the activity is * focused because {@code View.onFocusChanged()} indirectly calls * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to * explicitly create it (for example, a custom view developer could offer a contextual menu action * in a text-field view to let users manually request autofill). * * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure} * that represents the view hierarchy by calling * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in * the hierarchy. * * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which * parses it looking for views that can be autofilled. If the service finds such views, it returns * a data structure to the Android System containing the following optional info: * * <ul> * <li>Datasets used to autofill subsets of views in the activity. * <li>Id of views that the service can save their values for future autofilling. * </ul> * * <p>When the service returns datasets, the Android System displays an autofill dataset picker * UI associated with the view, when the view is focused on and is part of a dataset. * The application can be notified when the UI is shown by registering an * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user * selects a dataset from the UI, all views present in the dataset are autofilled, through * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. * * <p>When the service returns ids of savable views, the Android System keeps track of changes * made to these views, so they can be used to determine if the autofill save UI is shown later. * * <p>The context is then finished when one of the following occurs: * * <ul> * <li>{@link #commit()} is called or all savable views are gone. * <li>{@link #cancel()} is called. * </ul> * * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System * shows an autofill save UI if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * * <h3 id="additional-notes">Additional notes</h3> * * <p>It is safe to call <code>AutofillManager</code> methods from any thread. */
@SystemService(Context.AUTOFILL_MANAGER_SERVICE) @RequiresFeature(PackageManager.FEATURE_AUTOFILL) public final class AutofillManager { private static final String TAG = "AutofillManager";
Intent extra: The assist structure which captures the filled screen.

Type: AssistStructure

/** * Intent extra: The assist structure which captures the filled screen. * * <p> * Type: {@link android.app.assist.AssistStructure} */
public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
Intent extra: The result of an authentication operation. It is either a fully populated FillResponse or a fully populated Dataset if a response or a dataset is being authenticated respectively.

Type: FillResponse or a Dataset

/** * Intent extra: The result of an authentication operation. It is * either a fully populated {@link android.service.autofill.FillResponse} * or a fully populated {@link android.service.autofill.Dataset} if * a response or a dataset is being authenticated respectively. * * <p> * Type: {@link android.service.autofill.FillResponse} or a * {@link android.service.autofill.Dataset} */
public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
Intent extra: The optional extras provided by the AutofillService.

For example, when the service responds to a FillCallback.onSuccess(FillResponse) with a FillResponse that requires authentication, the Intent that launches the service authentication will contain the Bundle set by Builder.setClientState(Bundle) on this extra.

On Android VERSION_CODES.P and higher, the autofill service can also add this bundle to the Intent set as the result for an authentication request, so the bundle can be recovered later on SaveRequest.getClientState().

Type: Bundle

/** * Intent extra: The optional extras provided by the * {@link android.service.autofill.AutofillService}. * * <p>For example, when the service responds to a {@link * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with * a {@code FillResponse} that requires authentication, the Intent that launches the * service authentication will contain the Bundle set by * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. * * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service * can also add this bundle to the {@link Intent} set as the * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, * so the bundle can be recovered later on * {@link android.service.autofill.SaveRequest#getClientState()}. * * <p> * Type: {@link android.os.Bundle} */
public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE";
@hide
/** @hide */
public static final String EXTRA_RESTORE_SESSION_TOKEN = "android.view.autofill.extra.RESTORE_SESSION_TOKEN"; private static final String SESSION_ID_TAG = "android:sessionId"; private static final String STATE_TAG = "android:state"; private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
@hide
/** @hide */
public static final int ACTION_START_SESSION = 1;
@hide
/** @hide */
public static final int ACTION_VIEW_ENTERED = 2;
@hide
/** @hide */
public static final int ACTION_VIEW_EXITED = 3;
@hide
/** @hide */
public static final int ACTION_VALUE_CHANGED = 4;
@hide
/** @hide */
public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
@hide
/** @hide */
public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
@hide
/** @hide */
public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
Which bits in an authentication id are used for the dataset id
/** Which bits in an authentication id are used for the dataset id */
private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
How many bits in an authentication id are used for the dataset id
/** How many bits in an authentication id are used for the dataset id */
private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
@hideThe index for an undefined data set
/** @hide The index for an undefined data set */
public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
Used on onPendingSaveUi(int, IBinder) to cancel the pending UI.
@hide
/** * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI. * * @hide */
public static final int PENDING_UI_OPERATION_CANCEL = 1;
Used on onPendingSaveUi(int, IBinder) to restore the pending UI.
@hide
/** * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI. * * @hide */
public static final int PENDING_UI_OPERATION_RESTORE = 2;
Initial state of the autofill context, set when there is no session (i.e., when mSessionId is NO_SESSION).

In this state, app callbacks (such as notifyViewEntered(View)) are notified to the server.

@hide
/** * Initial state of the autofill context, set when there is no session (i.e., when * {@link #mSessionId} is {@link #NO_SESSION}). * * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to * the server. * * @hide */
public static final int STATE_UNKNOWN = 0;
State where the autofill context hasn't been finished nor canceled yet.
@hide
/** * State where the autofill context hasn't been {@link #commit() finished} nor * {@link #cancel() canceled} yet. * * @hide */
public static final int STATE_ACTIVE = 1;
State where the autofill context was finished by the server because the autofill service could not autofill the activity.

In this state, most apps callback (such as notifyViewEntered(View)) are ignored, exception requestAutofill(View) (and requestAutofill(View, int, Rect)).

@hide
/** * State where the autofill context was finished by the server because the autofill * service could not autofill the activity. * * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). * * @hide */
public static final int STATE_FINISHED = 2;
State where the autofill context has been finished but the server still has a session because the Save UI hasn't been dismissed yet.
@hide
/** * State where the autofill context has been {@link #commit() finished} but the server still has * a session because the Save UI hasn't been dismissed yet. * * @hide */
public static final int STATE_SHOWING_SAVE_UI = 3;
State where the autofill is disabled because the service cannot autofill the activity at all.

In this state, every call is ignored, even requestAutofill(View) (and requestAutofill(View, int, Rect)).

@hide
/** * State where the autofill is disabled because the service cannot autofill the activity at all. * * <p>In this state, every call is ignored, even {@link #requestAutofill(View)} * (and {@link #requestAutofill(View, int, Rect)}). * * @hide */
public static final int STATE_DISABLED_BY_SERVICE = 4;
Same as STATE_UNKNOWN, but used on AutofillManagerClient.setSessionFinished(int) when the session was finished because the URL bar changed on client mode
@hide
/** * Same as {@link #STATE_UNKNOWN}, but used on * {@link AutofillManagerClient#setSessionFinished(int)} when the session was finished because * the URL bar changed on client mode * * @hide */
public static final int STATE_UNKNOWN_COMPAT_MODE = 5;
Timeout in ms for calls to the field classification service.
@hide
/** * Timeout in ms for calls to the field classification service. * @hide */
public static final int FC_SERVICE_TIMEOUT = 5000;
Makes an authentication id from a request id and a dataset id.
Params:
  • requestId – The request id.
  • datasetId – The dataset id.
Returns:The authentication id.
@hide
/** * Makes an authentication id from a request id and a dataset id. * * @param requestId The request id. * @param datasetId The dataset id. * @return The authentication id. * @hide */
public static int makeAuthenticationId(int requestId, int datasetId) { return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT) | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK); }
Gets the request id from an authentication id.
Params:
  • authRequestId – The authentication id.
Returns:The request id.
@hide
/** * Gets the request id from an authentication id. * * @param authRequestId The authentication id. * @return The request id. * @hide */
public static int getRequestIdFromAuthenticationId(int authRequestId) { return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT); }
Gets the dataset id from an authentication id.
Params:
  • authRequestId – The authentication id.
Returns:The dataset id.
@hide
/** * Gets the dataset id from an authentication id. * * @param authRequestId The authentication id. * @return The dataset id. * @hide */
public static int getDatasetIdFromAuthenticationId(int authRequestId) { return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK); } private final MetricsLogger mMetricsLogger = new MetricsLogger();
There is currently no session running. {@hide}
/** * There is currently no session running. * {@hide} */
public static final int NO_SESSION = Integer.MIN_VALUE; private final IAutoFillManager mService; private final Object mLock = new Object(); @GuardedBy("mLock") private IAutoFillManagerClient mServiceClient; @GuardedBy("mLock") private Cleaner mServiceClientCleaner; @GuardedBy("mLock") private AutofillCallback mCallback; private final Context mContext; @GuardedBy("mLock") private int mSessionId = NO_SESSION; @GuardedBy("mLock") private int mState = STATE_UNKNOWN; @GuardedBy("mLock") private boolean mEnabled;
If a view changes to this mapping the autofill operation was successful
/** If a view changes to this mapping the autofill operation was successful */
@GuardedBy("mLock") @Nullable private ParcelableMap mLastAutofilledData;
If view tracking is enabled, contains the tracking state
/** If view tracking is enabled, contains the tracking state */
@GuardedBy("mLock") @Nullable private TrackedViews mTrackedViews;
Views that are only tracked because they are fillable and could be anchoring the UI.
/** Views that are only tracked because they are fillable and could be anchoring the UI. */
@GuardedBy("mLock") @Nullable private ArraySet<AutofillId> mFillableIds;
id of last requested autofill ui
/** id of last requested autofill ui */
@Nullable private AutofillId mIdShownFillUi;
Views that were already "entered" - if they're entered again when the session is not active, they're ignored
/** * Views that were already "entered" - if they're entered again when the session is not active, * they're ignored * */
@GuardedBy("mLock") @Nullable private ArraySet<AutofillId> mEnteredIds;
If set, session is commited when the field is clicked.
/** If set, session is commited when the field is clicked. */
@GuardedBy("mLock") @Nullable private AutofillId mSaveTriggerId;
set to true when onInvisibleForAutofill is called, used by onAuthenticationResult
/** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */
@GuardedBy("mLock") private boolean mOnInvisibleCalled;
If set, session is commited when the activity is finished; otherwise session is canceled.
/** If set, session is commited when the activity is finished; otherwise session is canceled. */
@GuardedBy("mLock") private boolean mSaveOnFinish;
If compatibility mode is enabled - this is a bridge to interact with a11y
/** If compatibility mode is enabled - this is a bridge to interact with a11y */
@GuardedBy("mLock") private CompatibilityBridge mCompatibilityBridge;
@hide
/** @hide */
public interface AutofillClient {
Asks the client to start an authentication flow.
Params:
  • authenticationId – A unique id of the authentication operation.
  • intent – The authentication intent.
  • fillInIntent – The authentication fill-in intent.
/** * Asks the client to start an authentication flow. * * @param authenticationId A unique id of the authentication operation. * @param intent The authentication intent. * @param fillInIntent The authentication fill-in intent. */
void autofillClientAuthenticate(int authenticationId, IntentSender intent, Intent fillInIntent);
Tells the client this manager has state to be reset.
/** * Tells the client this manager has state to be reset. */
void autofillClientResetableStateAvailable();
Request showing the autofill UI.
Params:
  • anchor – The real view the UI needs to anchor to.
  • width – The width of the fill UI content.
  • height – The height of the fill UI content.
  • virtualBounds – The bounds of the virtual decendant of the anchor.
  • presenter – The presenter that controls the fill UI window.
Returns:Whether the UI was shown.
/** * Request showing the autofill UI. * * @param anchor The real view the UI needs to anchor to. * @param width The width of the fill UI content. * @param height The height of the fill UI content. * @param virtualBounds The bounds of the virtual decendant of the anchor. * @param presenter The presenter that controls the fill UI window. * @return Whether the UI was shown. */
boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, int height, @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
Dispatch unhandled keyevent from Autofill window
Params:
  • anchor – The real view the UI needs to anchor to.
  • keyEvent – Unhandled KeyEvent from autofill window.
/** * Dispatch unhandled keyevent from Autofill window * @param anchor The real view the UI needs to anchor to. * @param keyEvent Unhandled KeyEvent from autofill window. */
void autofillClientDispatchUnhandledKey(@NonNull View anchor, @NonNull KeyEvent keyEvent);
Request hiding the autofill UI.
Returns:Whether the UI was hidden.
/** * Request hiding the autofill UI. * * @return Whether the UI was hidden. */
boolean autofillClientRequestHideFillUi();
Gets whether the fill UI is currenlty being shown.
Returns:Whether the fill UI is currently being shown
/** * Gets whether the fill UI is currenlty being shown. * * @return Whether the fill UI is currently being shown */
boolean autofillClientIsFillUiShowing();
Checks if views are currently attached and visible.
Returns:And array with true iff the view is attached or visible
/** * Checks if views are currently attached and visible. * * @return And array with {@code true} iff the view is attached or visible */
@NonNull boolean[] autofillClientGetViewVisibility(@NonNull AutofillId[] autofillIds);
Checks is the client is currently visible as understood by autofill.
Returns:true if the client is currently visible
/** * Checks is the client is currently visible as understood by autofill. * * @return {@code true} if the client is currently visible */
boolean autofillClientIsVisibleForAutofill();
Client might disable enter/exit event e.g. when activity is paused.
/** * Client might disable enter/exit event e.g. when activity is paused. */
boolean isDisablingEnterExitEventForAutofill();
Finds views by traversing the hierarchies of the client.
Params:
  • autofillIds – The autofill ids of the views to find
Returns:And array containing the views (empty if no views found).
/** * Finds views by traversing the hierarchies of the client. * * @param autofillIds The autofill ids of the views to find * * @return And array containing the views (empty if no views found). */
@NonNull View[] autofillClientFindViewsByAutofillIdTraversal( @NonNull AutofillId[] autofillIds);
Finds a view by traversing the hierarchies of the client.
Params:
  • autofillId – The autofill id of the views to find
Returns:The view, or null if not found
/** * Finds a view by traversing the hierarchies of the client. * * @param autofillId The autofill id of the views to find * * @return The view, or {@code null} if not found */
@Nullable View autofillClientFindViewByAutofillIdTraversal(@NonNull AutofillId autofillId);
Finds a view by a11y id in a given client window.
Params:
  • viewId – The accessibility id of the views to find
  • windowId – The accessibility window id where to search
Returns:The view, or null if not found
/** * Finds a view by a11y id in a given client window. * * @param viewId The accessibility id of the views to find * @param windowId The accessibility window id where to search * * @return The view, or {@code null} if not found */
@Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
Runs the specified action on the UI thread.
/** * Runs the specified action on the UI thread. */
void autofillClientRunOnUiThread(Runnable action);
Gets the complete component name of this client.
/** * Gets the complete component name of this client. */
ComponentName autofillClientGetComponentName();
Gets the activity token
/** * Gets the activity token */
@Nullable IBinder autofillClientGetActivityToken();
Returns:Whether compatibility mode is enabled.
/** * @return Whether compatibility mode is enabled. */
boolean autofillClientIsCompatibilityModeEnabled();
Gets the next unique autofill ID.

Typically used to manage views whose content is recycled - see View.setAutofillId(AutofillId) for more info.

Returns:An ID that is unique in the activity.
/** * Gets the next unique autofill ID. * * <p>Typically used to manage views whose content is recycled - see * {@link View#setAutofillId(AutofillId)} for more info. * * @return An ID that is unique in the activity. */
@Nullable AutofillId autofillClientGetNextAutofillId(); }
@hide
/** * @hide */
public AutofillManager(Context context, IAutoFillManager service) { mContext = Preconditions.checkNotNull(context, "context cannot be null"); mService = service; }
@hide
/** * @hide */
public void enableCompatibilityMode() { synchronized (mLock) { // The accessibility manager is a singleton so we may need to plug // different bridge based on which activity is currently focused // in the current process. Since compat would be rarely used, just // create and register a new instance every time. if (sDebug) { Slog.d(TAG, "creating CompatibilityBridge for " + mContext); } mCompatibilityBridge = new CompatibilityBridge(); } }
Restore state after activity lifecycle
Params:
  • savedInstanceState – The state to be restored {@hide}
/** * Restore state after activity lifecycle * * @param savedInstanceState The state to be restored * * {@hide} */
public void onCreate(Bundle savedInstanceState) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); if (isActiveLocked()) { Log.w(TAG, "New session was started before onCreate()"); return; } mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION); mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); if (mSessionId != NO_SESSION) { ensureServiceClientAddedIfNeededLocked(); final AutofillClient client = getClient(); if (client != null) { try { final boolean sessionWasRestored = mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(), mServiceClient.asBinder()); if (!sessionWasRestored) { Log.w(TAG, "Session " + mSessionId + " could not be restored"); mSessionId = NO_SESSION; mState = STATE_UNKNOWN; } else { if (sDebug) { Log.d(TAG, "session " + mSessionId + " was restored"); } client.autofillClientResetableStateAvailable(); } } catch (RemoteException e) { Log.e(TAG, "Could not figure out if there was an autofill session", e); } } } } }
Called once the client becomes visible.
See Also:
  • {@hide}
/** * Called once the client becomes visible. * * @see AutofillClient#autofillClientIsVisibleForAutofill() * * {@hide} */
public void onVisibleForAutofill() { // This gets called when the client just got visible at which point the visibility // of the tracked views may not have been computed (due to a pending layout, etc). // While generally we have no way to know when the UI has settled. We will evaluate // the tracked views state at the end of next frame to guarantee that everything // that may need to be laid out is laid out. Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { synchronized (mLock) { if (mEnabled && isActiveLocked() && mTrackedViews != null) { mTrackedViews.onVisibleForAutofillChangedLocked(); } } }, null); }
Called once the client becomes invisible.
See Also:
  • {@hide}
/** * Called once the client becomes invisible. * * @see AutofillClient#autofillClientIsVisibleForAutofill() * * {@hide} */
public void onInvisibleForAutofill() { synchronized (mLock) { mOnInvisibleCalled = true; } }
Save state before activity lifecycle
Params:
  • outState – Place to store the state {@hide}
/** * Save state before activity lifecycle * * @param outState Place to store the state * * {@hide} */
public void onSaveInstanceState(Bundle outState) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { if (mSessionId != NO_SESSION) { outState.putInt(SESSION_ID_TAG, mSessionId); } if (mState != STATE_UNKNOWN) { outState.putInt(STATE_TAG, mState); } if (mLastAutofilledData != null) { outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); } } }
@hide
/** * @hide */
@GuardedBy("mLock") public boolean isCompatibilityModeEnabledLocked() { return mCompatibilityBridge != null; }
Checks whether autofill is enabled for the current user.

Typically used to determine whether the option to explicitly request autofill should be offered - see requestAutofill(View).

Returns:whether autofill is enabled for the current user.
/** * Checks whether autofill is enabled for the current user. * * <p>Typically used to determine whether the option to explicitly request autofill should * be offered - see {@link #requestAutofill(View)}. * * @return whether autofill is enabled for the current user. */
public boolean isEnabled() { if (!hasAutofillFeature()) { return false; } synchronized (mLock) { if (isDisabledByServiceLocked()) { return false; } ensureServiceClientAddedIfNeededLocked(); return mEnabled; } }
Should always be called from AutofillService.getFillEventHistory().
@hide
/** * Should always be called from {@link AutofillService#getFillEventHistory()}. * * @hide */
@Nullable public FillEventHistory getFillEventHistory() { try { return mService.getFillEventHistory(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; } }
Explicitly requests a new autofill context.

Normally, the autofill context is automatically started if necessary when notifyViewEntered(View) is called, but this method should be used in the cases where it must be explicitly started. For example, when the view offers an AUTOFILL option on its contextual overflow menu, and the user selects it.

Params:
  • view – view requesting the new autofill context.
/** * Explicitly requests a new autofill context. * * <p>Normally, the autofill context is automatically started if necessary when * {@link #notifyViewEntered(View)} is called, but this method should be used in the * cases where it must be explicitly started. For example, when the view offers an AUTOFILL * option on its contextual overflow menu, and the user selects it. * * @param view view requesting the new autofill context. */
public void requestAutofill(@NonNull View view) { notifyViewEntered(view, FLAG_MANUAL_REQUEST); }
Explicitly requests a new autofill context for virtual views.

Normally, the autofill context is automatically started if necessary when notifyViewEntered(View, int, Rect) is called, but this method should be used in the cases where it must be explicitly started. For example, when the virtual view offers an AUTOFILL option on its contextual overflow menu, and the user selects it.

The virtual view boundaries must be absolute screen coordinates. For example, if the parent view uses bounds to draw the virtual view inside its Canvas, the absolute bounds could be calculated by:

  int offset[] = new int[2];
  getLocationOnScreen(offset);
  Rect absBounds = new Rect(bounds.left + offset[0],
      bounds.top + offset[1],
      bounds.right + offset[0], bounds.bottom + offset[1]);
Params:
  • view – the virtual view parent.
  • virtualId – id identifying the virtual child inside the parent view.
  • absBounds – absolute boundaries of the virtual view in the screen.
/** * Explicitly requests a new autofill context for virtual views. * * <p>Normally, the autofill context is automatically started if necessary when * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the * cases where it must be explicitly started. For example, when the virtual view offers an * AUTOFILL option on its contextual overflow menu, and the user selects it. * * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the * parent view uses {@code bounds} to draw the virtual view inside its Canvas, * the absolute bounds could be calculated by: * * <pre class="prettyprint"> * int offset[] = new int[2]; * getLocationOnScreen(offset); * Rect absBounds = new Rect(bounds.left + offset[0], * bounds.top + offset[1], * bounds.right + offset[0], bounds.bottom + offset[1]); * </pre> * * @param view the virtual view parent. * @param virtualId id identifying the virtual child inside the parent view. * @param absBounds absolute boundaries of the virtual view in the screen. */
public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) { notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST); }
Called when a View that supports autofill is entered.
Params:
  • view – View that was entered.
/** * Called when a {@link View} that supports autofill is entered. * * @param view {@link View} that was entered. */
public void notifyViewEntered(@NonNull View view) { notifyViewEntered(view, 0); } @GuardedBy("mLock") private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) { if (isDisabledByServiceLocked()) { if (sVerbose) { Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id + ") on state " + getStateAsStringLocked() + " because disabled by svc"); } return true; } if (isFinishedLocked()) { // Session already finished: ignore if automatic request and view already entered if ((flags & FLAG_MANUAL_REQUEST) == 0 && mEnteredIds != null && mEnteredIds.contains(id)) { if (sVerbose) { Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + id + ") on state " + getStateAsStringLocked() + " because view was already entered: " + mEnteredIds); } return true; } } if (sVerbose) { Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + id + ", state " + getStateAsStringLocked() + ", enteredIds=" + mEnteredIds); } return false; } private boolean isClientVisibleForAutofillLocked() { final AutofillClient client = getClient(); return client != null && client.autofillClientIsVisibleForAutofill(); } private boolean isClientDisablingEnterExitEvent() { final AutofillClient client = getClient(); return client != null && client.isDisablingEnterExitEventForAutofill(); } private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } AutofillCallback callback; synchronized (mLock) { callback = notifyViewEnteredLocked(view, flags); } if (callback != null) { mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); } }
Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
@GuardedBy("mLock") private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { final AutofillId id = view.getAutofillId(); if (shouldIgnoreViewEnteredLocked(id, flags)) return null; AutofillCallback callback = null; ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { if (mCallback != null) { callback = mCallback; } } else { // don't notify entered when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillValue value = view.getAutofillValue(); if (!isActiveLocked()) { // Starts new session. startSessionLocked(id, null, value, flags); } else { // Update focus on existing session. updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags); } addEnteredIdLocked(id); } } return callback; }
Called when a View that supports autofill is exited.
Params:
  • view – View that was exited.
/** * Called when a {@link View} that supports autofill is exited. * * @param view {@link View} that was exited. */
public void notifyViewExited(@NonNull View view) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { notifyViewExitedLocked(view); } } @GuardedBy("mLock") void notifyViewExitedLocked(@NonNull View view) { ensureServiceClientAddedIfNeededLocked(); if (mEnabled && isActiveLocked()) { // dont notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = view.getAutofillId(); // Update focus on existing session. updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); } } }
Called when a view's visibility changed.
Params:
  • view – View that was exited.
  • isVisible – visible if the view is visible in the view hierarchy.
/** * Called when a {@link View view's} visibility changed. * * @param view {@link View} that was exited. * @param isVisible visible if the view is visible in the view hierarchy. */
public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) { notifyViewVisibilityChangedInternal(view, 0, isVisible, false); }
Called when a virtual view's visibility changed.
Params:
  • view – View that was exited.
  • virtualId – id identifying the virtual child inside the parent view.
  • isVisible – visible if the view is visible in the view hierarchy.
/** * Called when a virtual view's visibility changed. * * @param view {@link View} that was exited. * @param virtualId id identifying the virtual child inside the parent view. * @param isVisible visible if the view is visible in the view hierarchy. */
public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) { notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true); }
Called when a view/virtual view's visibility changed.
Params:
  • view – View that was exited.
  • virtualId – id identifying the virtual child inside the parent view.
  • isVisible – visible if the view is visible in the view hierarchy.
  • virtual – Whether the view is virtual.
/** * Called when a view/virtual view's visibility changed. * * @param view {@link View} that was exited. * @param virtualId id identifying the virtual child inside the parent view. * @param isVisible visible if the view is visible in the view hierarchy. * @param virtual Whether the view is virtual. */
private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId, boolean isVisible, boolean virtual) { synchronized (mLock) { if (mEnabled && isActiveLocked()) { final AutofillId id = virtual ? getAutofillId(view, virtualId) : view.getAutofillId(); if (sVerbose) Log.v(TAG, "visibility changed for " + id + ": " + isVisible); if (!isVisible && mFillableIds != null) { if (mFillableIds.contains(id)) { if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible"); requestHideFillUi(id, view); } } if (mTrackedViews != null) { mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); } else if (sVerbose) { Log.v(TAG, "Ignoring visibility change on " + id + ": no tracked views"); } } } }
Called when a virtual view that supports autofill is entered.

The virtual view boundaries must be absolute screen coordinates. For example, if the parent, non-virtual view uses bounds to draw the virtual view inside its Canvas, the absolute bounds could be calculated by:

  int offset[] = new int[2];
  getLocationOnScreen(offset);
  Rect absBounds = new Rect(bounds.left + offset[0],
      bounds.top + offset[1],
      bounds.right + offset[0], bounds.bottom + offset[1]);
Params:
  • view – the virtual view parent.
  • virtualId – id identifying the virtual child inside the parent view.
  • absBounds – absolute boundaries of the virtual view in the screen.
/** * Called when a virtual view that supports autofill is entered. * * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas, * the absolute bounds could be calculated by: * * <pre class="prettyprint"> * int offset[] = new int[2]; * getLocationOnScreen(offset); * Rect absBounds = new Rect(bounds.left + offset[0], * bounds.top + offset[1], * bounds.right + offset[0], bounds.bottom + offset[1]); * </pre> * * @param view the virtual view parent. * @param virtualId id identifying the virtual child inside the parent view. * @param absBounds absolute boundaries of the virtual view in the screen. */
public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) { notifyViewEntered(view, virtualId, absBounds, 0); } private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) { if (!hasAutofillFeature()) { return; } AutofillCallback callback; synchronized (mLock) { callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); } if (callback != null) { callback.onAutofillEvent(view, virtualId, AutofillCallback.EVENT_INPUT_UNAVAILABLE); } }
Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
@GuardedBy("mLock") private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, int flags) { final AutofillId id = getAutofillId(view, virtualId); AutofillCallback callback = null; if (shouldIgnoreViewEnteredLocked(id, flags)) return callback; ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { if (mCallback != null) { callback = mCallback; } } else { // don't notify entered when Activity is already in background if (!isClientDisablingEnterExitEvent()) { if (!isActiveLocked()) { // Starts new session. startSessionLocked(id, bounds, null, flags); } else { // Update focus on existing session. updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags); } addEnteredIdLocked(id); } } return callback; } @GuardedBy("mLock") private void addEnteredIdLocked(@NonNull AutofillId id) { if (mEnteredIds == null) { mEnteredIds = new ArraySet<>(1); } mEnteredIds.add(id); }
Called when a virtual view that supports autofill is exited.
Params:
  • view – the virtual view parent.
  • virtualId – id identifying the virtual child inside the parent view.
/** * Called when a virtual view that supports autofill is exited. * * @param view the virtual view parent. * @param virtualId id identifying the virtual child inside the parent view. */
public void notifyViewExited(@NonNull View view, int virtualId) { if (sVerbose) Log.v(TAG, "notifyViewExited(" + view.getAutofillId() + ", " + virtualId); if (!hasAutofillFeature()) { return; } synchronized (mLock) { notifyViewExitedLocked(view, virtualId); } } @GuardedBy("mLock") private void notifyViewExitedLocked(@NonNull View view, int virtualId) { ensureServiceClientAddedIfNeededLocked(); if (mEnabled && isActiveLocked()) { // don't notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); // Update focus on existing session. updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0); } } }
Called to indicate the value of an autofillable View changed.
Params:
  • view – view whose value changed.
/** * Called to indicate the value of an autofillable {@link View} changed. * * @param view view whose value changed. */
public void notifyValueChanged(View view) { if (!hasAutofillFeature()) { return; } AutofillId id = null; boolean valueWasRead = false; AutofillValue value = null; synchronized (mLock) { // If the session is gone some fields might still be highlighted, hence we have to // remove the isAutofilled property even if no sessions are active. if (mLastAutofilledData == null) { view.setAutofilled(false); } else { id = view.getAutofillId(); if (mLastAutofilledData.containsKey(id)) { value = view.getAutofillValue(); valueWasRead = true; if (Objects.equals(mLastAutofilledData.get(id), value)) { view.setAutofilled(true); } else { view.setAutofilled(false); mLastAutofilledData.remove(id); } } else { view.setAutofilled(false); } } if (!mEnabled || !isActiveLocked()) { if (sVerbose) { Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + "): ignoring on state " + getStateAsStringLocked()); } return; } if (id == null) { id = view.getAutofillId(); } if (!valueWasRead) { value = view.getAutofillValue(); } updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); } }
Called to indicate the value of an autofillable virtual view has changed.
Params:
  • view – the virtual view parent.
  • virtualId – id identifying the virtual child inside the parent view.
  • value – new value of the child.
/** * Called to indicate the value of an autofillable virtual view has changed. * * @param view the virtual view parent. * @param virtualId id identifying the virtual child inside the parent view. * @param value new value of the child. */
public void notifyValueChanged(View view, int virtualId, AutofillValue value) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { if (!mEnabled || !isActiveLocked()) { if (sVerbose) { Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId + "): ignoring on state " + getStateAsStringLocked()); } return; } final AutofillId id = getAutofillId(view, virtualId); updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0); } }
Called to indicate a View is clicked.
Params:
  • view – view that has been clicked.
/** * Called to indicate a {@link View} is clicked. * * @param view view that has been clicked. */
public void notifyViewClicked(@NonNull View view) { notifyViewClicked(view.getAutofillId()); }
Called to indicate a virtual view has been clicked.
Params:
  • view – the virtual view parent.
  • virtualId – id identifying the virtual child inside the parent view.
/** * Called to indicate a virtual view has been clicked. * * @param view the virtual view parent. * @param virtualId id identifying the virtual child inside the parent view. */
public void notifyViewClicked(@NonNull View view, int virtualId) { notifyViewClicked(getAutofillId(view, virtualId)); } private void notifyViewClicked(AutofillId id) { if (!hasAutofillFeature()) { return; } if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); synchronized (mLock) { if (!mEnabled || !isActiveLocked()) { return; } if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { if (sDebug) Log.d(TAG, "triggering commit by click of " + id); commitLocked(); mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED)); } } }
Called by Activity to commit or cancel the session on finish.
@hide
/** * Called by {@link android.app.Activity} to commit or cancel the session on finish. * * @hide */
public void onActivityFinishing() { if (!hasAutofillFeature()) { return; } synchronized (mLock) { if (mSaveOnFinish) { if (sDebug) Log.d(TAG, "onActivityFinishing(): calling commitLocked()"); commitLocked(); } else { if (sDebug) Log.d(TAG, "onActivityFinishing(): calling cancelLocked()"); cancelLocked(); } } }
Called to indicate the current autofill context should be commited.

This method is typically called by Views that manage virtual views; for example, when the view is rendering an HTML page with a form and virtual views that represent the HTML elements, it should call this method after the form is submitted and another page is rendered.

Note: This method does not need to be called on regular application lifecycle methods such as Activity.finish().

/** * Called to indicate the current autofill context should be commited. * * <p>This method is typically called by {@link View Views} that manage virtual views; for * example, when the view is rendering an {@code HTML} page with a form and virtual views * that represent the HTML elements, it should call this method after the form is submitted and * another page is rendered. * * <p><b>Note:</b> This method does not need to be called on regular application lifecycle * methods such as {@link android.app.Activity#finish()}. */
public void commit() { if (!hasAutofillFeature()) { return; } if (sVerbose) Log.v(TAG, "commit() called by app"); synchronized (mLock) { commitLocked(); } } @GuardedBy("mLock") private void commitLocked() { if (!mEnabled && !isActiveLocked()) { return; } finishSessionLocked(); }
Called to indicate the current autofill context should be cancelled.

This method is typically called by Views that manage virtual views; for example, when the view is rendering an HTML page with a form and virtual views that represent the HTML elements, it should call this method if the user does not post the form but moves to another form in this page.

Note: This method does not need to be called on regular application lifecycle methods such as Activity.finish().

/** * Called to indicate the current autofill context should be cancelled. * * <p>This method is typically called by {@link View Views} that manage virtual views; for * example, when the view is rendering an {@code HTML} page with a form and virtual views * that represent the HTML elements, it should call this method if the user does not post the * form but moves to another form in this page. * * <p><b>Note:</b> This method does not need to be called on regular application lifecycle * methods such as {@link android.app.Activity#finish()}. */
public void cancel() { if (sVerbose) Log.v(TAG, "cancel() called by app"); if (!hasAutofillFeature()) { return; } synchronized (mLock) { cancelLocked(); } } @GuardedBy("mLock") private void cancelLocked() { if (!mEnabled && !isActiveLocked()) { return; } cancelSessionLocked(); }
@hide
/** @hide */
public void disableOwnedAutofillServices() { disableAutofillServices(); }
If the app calling this API has enabled autofill services they will be disabled.
/** * If the app calling this API has enabled autofill services they * will be disabled. */
public void disableAutofillServices() { if (!hasAutofillFeature()) { return; } try { mService.disableOwnedAutofillServices(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
Returns true if the calling application provides a AutofillService that is enabled for the current user, or false otherwise.
/** * Returns {@code true} if the calling application provides a {@link AutofillService} that is * enabled for the current user, or {@code false} otherwise. */
public boolean hasEnabledAutofillServices() { if (mService == null) return false; try { return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
Returns the component name of the AutofillService that is enabled for the current user.
/** * Returns the component name of the {@link AutofillService} that is enabled for the current * user. */
@Nullable public ComponentName getAutofillServiceComponentName() { if (mService == null) return null; try { return mService.getAutofillServiceComponentName(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
Gets the id of the UserData used for field classification.

This method is useful when the service must check the status of the UserData in the device without fetching the whole object.

Note: This method should only be called by an app providing an autofill service, and it's ignored if the caller currently doesn't have an enabled autofill service for the user.

Returns:id of the UserData previously set by setUserData(UserData) or null if it was reset or if the caller currently does not have an enabled autofill service for the user.
/** * Gets the id of the {@link UserData} used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * * <p>This method is useful when the service must check the status of the {@link UserData} in * the device without fetching the whole object. * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it's ignored if the caller currently doesn't have an enabled autofill service for * the user. * * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)} * or {@code null} if it was reset or if the caller currently does not have an enabled autofill * service for the user. */
@Nullable public String getUserDataId() { try { return mService.getUserDataId(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; } }
Gets the user data used for field classification.

Note: This method should only be called by an app providing an autofill service, and it's ignored if the caller currently doesn't have an enabled autofill service for the user.

Returns:value previously set by setUserData(UserData) or null if it was reset or if the caller currently does not have an enabled autofill service for the user.
/** * Gets the user data used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it's ignored if the caller currently doesn't have an enabled autofill service for * the user. * * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was * reset or if the caller currently does not have an enabled autofill service for the user. */
@Nullable public UserData getUserData() { try { return mService.getUserData(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; } }
Sets the UserData used for field classification

Note: This method should only be called by an app providing an autofill service, and it's ignored if the caller currently doesn't have an enabled autofill service for the user.

/** * Sets the {@link UserData} used for * <a href="AutofillService.html#FieldClassification">field classification</a> * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it's ignored if the caller currently doesn't have an enabled autofill service for * the user. */
public void setUserData(@Nullable UserData userData) { try { mService.setUserData(userData); } catch (RemoteException e) { e.rethrowFromSystemServer(); } }
Checks if field classification is enabled.

As field classification is an expensive operation, it could be disabled, either temporarily (for example, because the service exceeded a rate-limit threshold) or permanently (for example, because the device is a low-level device).

Note: This method should only be called by an app providing an autofill service, and it's ignored if the caller currently doesn't have an enabled autofill service for the user.

/** * Checks if <a href="AutofillService.html#FieldClassification">field classification</a> is * enabled. * * <p>As field classification is an expensive operation, it could be disabled, either * temporarily (for example, because the service exceeded a rate-limit threshold) or * permanently (for example, because the device is a low-level device). * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it's ignored if the caller currently doesn't have an enabled autofill service for * the user. */
public boolean isFieldClassificationEnabled() { try { return mService.isFieldClassificationEnabled(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return false; } }
Gets the name of the default algorithm used for field classification.

The default algorithm is used when the algorithm on UserData is invalid or not set.

Note: This method should only be called by an app providing an autofill service, and it's ignored if the caller currently doesn't have an enabled autofill service for the user.

/** * Gets the name of the default algorithm used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not * set. * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it's ignored if the caller currently doesn't have an enabled autofill service for * the user. */
@Nullable public String getDefaultFieldClassificationAlgorithm() { try { return mService.getDefaultFieldClassificationAlgorithm(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; } }
Gets the name of all algorithms currently available for field classification.

Note: This method should only be called by an app providing an autofill service, and it returns an empty list if the caller currently doesn't have an enabled autofill service for the user.

/** * Gets the name of all algorithms currently available for * <a href="AutofillService.html#FieldClassification">field classification</a>. * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, * and it returns an empty list if the caller currently doesn't have an enabled autofill service * for the user. */
@NonNull public List<String> getAvailableFieldClassificationAlgorithms() { final String[] algorithms; try { algorithms = mService.getAvailableFieldClassificationAlgorithms(); return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; } }
Returns true if autofill is supported by the current device and is supported for this user.

Autofill is typically supported, but it could be unsupported in cases like:

  1. Low-end devices.
  2. Device policy rules that forbid its usage.
/** * Returns {@code true} if autofill is supported by the current device and * is supported for this user. * * <p>Autofill is typically supported, but it could be unsupported in cases like: * <ol> * <li>Low-end devices. * <li>Device policy rules that forbid its usage. * </ol> */
public boolean isAutofillSupported() { if (mService == null) return false; try { return mService.isServiceSupported(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } // Note: don't need to use locked suffix because mContext is final. private AutofillClient getClient() { final AutofillClient client = mContext.getAutofillClient(); if (client == null && sDebug) { Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " + mContext); } return client; }
Check if autofill ui is showing, must be called on UI thread.
@hide
/** * Check if autofill ui is showing, must be called on UI thread. * @hide */
public boolean isAutofillUiShowing() { final AutofillClient client = mContext.getAutofillClient(); return client != null && client.autofillClientIsFillUiShowing(); }
@hide
/** @hide */
public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { if (!hasAutofillFeature()) { return; } // TODO: the result code is being ignored, so this method is not reliably // handling the cases where it's not RESULT_OK: it works fine if the service does not // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the // service set the extra and returned RESULT_CANCELED... if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); synchronized (mLock) { if (!isActiveLocked()) { return; } // If authenticate activity closes itself during onCreate(), there is no onStop/onStart // of app activity. We enforce enter event to re-show fill ui in such case. // CTS example: // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt if (!mOnInvisibleCalled && focusView != null && focusView.canNotifyAutofillEnterExitEvent()) { notifyViewExitedLocked(focusView); notifyViewEnteredLocked(focusView, 0); } if (data == null) { // data is set to null when result is not RESULT_OK return; } final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); if (newClientState != null) { responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); } try { mService.setAuthenticationResult(responseData, mSessionId, authenticationId, mContext.getUserId()); } catch (RemoteException e) { Log.e(TAG, "Error delivering authentication result", e); } } }
Gets the next unique autofill ID for the activity context.

Typically used to manage views whose content is recycled - see View.setAutofillId(AutofillId) for more info.

Returns:An ID that is unique in the activity, or null if autofill is not supported in the Context associated with this AutofillManager.
/** * Gets the next unique autofill ID for the activity context. * * <p>Typically used to manage views whose content is recycled - see * {@link View#setAutofillId(AutofillId)} for more info. * * @return An ID that is unique in the activity, or {@code null} if autofill is not supported in * the {@link Context} associated with this {@link AutofillManager}. */
@Nullable public AutofillId getNextAutofillId() { final AutofillClient client = getClient(); if (client == null) return null; final AutofillId id = client.autofillClientGetNextAutofillId(); if (id == null && sDebug) { Log.d(TAG, "getNextAutofillId(): client " + client + " returned null"); } return id; } private static AutofillId getAutofillId(View parent, int virtualId) { return new AutofillId(parent.getAutofillViewId(), virtualId); } @GuardedBy("mLock") private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { if (sVerbose) { Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value + ", flags=" + flags + ", state=" + getStateAsStringLocked() + ", compatMode=" + isCompatibilityModeEnabledLocked() + ", enteredIds=" + mEnteredIds); } if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { if (sVerbose) { Log.v(TAG, "not automatically starting session for " + id + " on state " + getStateAsStringLocked() + " and flags " + flags); } return; } try { final AutofillClient client = getClient(); if (client == null) return; // NOTE: getClient() already logged it.. mSessionId = mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, client.autofillClientGetComponentName(), isCompatibilityModeEnabledLocked()); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } client.autofillClientResetableStateAvailable(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @GuardedBy("mLock") private void finishSessionLocked() { if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); if (!isActiveLocked()) return; try { mService.finishSession(mSessionId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } resetSessionLocked(/* resetEnteredIds= */ true); } @GuardedBy("mLock") private void cancelSessionLocked() { if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); if (!isActiveLocked()) return; try { mService.cancelSession(mSessionId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } resetSessionLocked(/* resetEnteredIds= */ true); } @GuardedBy("mLock") private void resetSessionLocked(boolean resetEnteredIds) { mSessionId = NO_SESSION; mState = STATE_UNKNOWN; mTrackedViews = null; mFillableIds = null; mSaveTriggerId = null; mIdShownFillUi = null; if (resetEnteredIds) { mEnteredIds = null; } } @GuardedBy("mLock") private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, int flags) { if (sVerbose) { Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value + ", action=" + action + ", flags=" + flags); } boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0; try { if (restartIfNecessary) { final AutofillClient client = getClient(); if (client == null) return; // NOTE: getClient() already logd it.. final int newId = mService.updateOrRestartSession( client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, client.autofillClientGetComponentName(), mSessionId, action, isCompatibilityModeEnabledLocked()); if (newId != mSessionId) { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; client.autofillClientResetableStateAvailable(); } } else { mService.updateSession(mSessionId, id, bounds, value, action, flags, mContext.getUserId()); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @GuardedBy("mLock") private void ensureServiceClientAddedIfNeededLocked() { if (getClient() == null) { return; } if (mServiceClient == null) { mServiceClient = new AutofillManagerClient(this); try { final int userId = mContext.getUserId(); final int flags = mService.addClient(mServiceClient, userId); mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; final IAutoFillManager service = mService; final IAutoFillManagerClient serviceClient = mServiceClient; mServiceClientCleaner = Cleaner.create(this, () -> { try { service.removeClient(serviceClient, userId); } catch (RemoteException e) { } }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }
Registers a AutofillCallback to receive autofill events.
Params:
  • callback – callback to receive events.
/** * Registers a {@link AutofillCallback} to receive autofill events. * * @param callback callback to receive events. */
public void registerCallback(@Nullable AutofillCallback callback) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { if (callback == null) return; final boolean hadCallback = mCallback != null; mCallback = callback; if (!hadCallback) { try { mService.setHasCallback(mSessionId, mContext.getUserId(), true); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } }
Unregisters a AutofillCallback to receive autofill events.
Params:
  • callback – callback to stop receiving events.
/** * Unregisters a {@link AutofillCallback} to receive autofill events. * * @param callback callback to stop receiving events. */
public void unregisterCallback(@Nullable AutofillCallback callback) { if (!hasAutofillFeature()) { return; } synchronized (mLock) { if (callback == null || mCallback == null || callback != mCallback) return; mCallback = null; try { mService.setHasCallback(mSessionId, mContext.getUserId(), false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) { final View anchor = findView(id); if (anchor == null) { return; } AutofillCallback callback = null; synchronized (mLock) { if (mSessionId == sessionId) { AutofillClient client = getClient(); if (client != null) { if (client.autofillClientRequestShowFillUi(anchor, width, height, anchorBounds, presenter)) { callback = mCallback; mIdShownFillUi = id; } } } } if (callback != null) { if (id.isVirtual()) { callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_SHOWN); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); } } } private void authenticate(int sessionId, int authenticationId, IntentSender intent, Intent fillInIntent) { synchronized (mLock) { if (sessionId == mSessionId) { final AutofillClient client = getClient(); if (client != null) { // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() // before onAuthenticationResult() mOnInvisibleCalled = false; client.autofillClientAuthenticate(authenticationId, intent, fillInIntent); } } } } private void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent keyEvent) { final View anchor = findView(id); if (anchor == null) { return; } AutofillCallback callback = null; synchronized (mLock) { if (mSessionId == sessionId) { AutofillClient client = getClient(); if (client != null) { client.autofillClientDispatchUnhandledKey(anchor, keyEvent); } } } }
@hide
/** @hide */
public static final int SET_STATE_FLAG_ENABLED = 0x01;
@hide
/** @hide */
public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
@hide
/** @hide */
public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
@hide
/** @hide */
public static final int SET_STATE_FLAG_DEBUG = 0x08;
@hide
/** @hide */
public static final int SET_STATE_FLAG_VERBOSE = 0x10; private void setState(int flags) { if (sVerbose) Log.v(TAG, "setState(" + flags + ")"); synchronized (mLock) { mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { // Reset the session state resetSessionLocked(/* resetEnteredIds= */ true); } if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { // Reset connection to system mServiceClient = null; if (mServiceClientCleaner != null) { mServiceClientCleaner.clean(); mServiceClientCleaner = null; } } } sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; }
Sets a view as autofilled if the current value is the {code targetValue}.
Params:
  • view – The view that is to be autofilled
  • targetValue – The value we want to fill into view
/** * Sets a view as autofilled if the current value is the {code targetValue}. * * @param view The view that is to be autofilled * @param targetValue The value we want to fill into view */
private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { AutofillValue currentValue = view.getAutofillValue(); if (Objects.equals(currentValue, targetValue)) { synchronized (mLock) { if (mLastAutofilledData == null) { mLastAutofilledData = new ParcelableMap(1); } mLastAutofilledData.put(view.getAutofillId(), targetValue); } view.setAutofilled(true); } } private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { synchronized (mLock) { if (sessionId != mSessionId) { return; } final AutofillClient client = getClient(); if (client == null) { return; } final int itemCount = ids.size(); int numApplied = 0; ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( Helper.toArray(ids)); ArrayList<AutofillId> failedIds = null; for (int i = 0; i < itemCount; i++) { final AutofillId id = ids.get(i); final AutofillValue value = values.get(i); final int viewId = id.getViewId(); final View view = views[i]; if (view == null) { // Most likely view has been removed after the initial request was sent to the // the service; this is fine, but we need to update the view status in the // server side so it can be triggered again. Log.d(TAG, "autofill(): no View with id " + id); if (failedIds == null) { failedIds = new ArrayList<>(); } failedIds.add(id); continue; } if (id.isVirtual()) { if (virtualValues == null) { // Most likely there will be just one view with virtual children. virtualValues = new ArrayMap<>(1); } SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); if (valuesByParent == null) { // We don't know the size yet, but usually it will be just a few fields... valuesByParent = new SparseArray<>(5); virtualValues.put(view, valuesByParent); } valuesByParent.put(id.getVirtualChildId(), value); } else { // Mark the view as to be autofilled with 'value' if (mLastAutofilledData == null) { mLastAutofilledData = new ParcelableMap(itemCount - i); } mLastAutofilledData.put(id, value); view.autofill(value); // Set as autofilled if the values match now, e.g. when the value was updated // synchronously. // If autofill happens async, the view is set to autofilled in // notifyValueChanged. setAutofilledIfValuesIs(view, value); numApplied++; } } if (failedIds != null) { if (sVerbose) { Log.v(TAG, "autofill(): total failed views: " + failedIds); } try { mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId()); } catch (RemoteException e) { // In theory, we could ignore this error since it's not a big deal, but // in reality, we rather crash the app anyways, as the failure could be // a consequence of something going wrong on the server side... e.rethrowFromSystemServer(); } } if (virtualValues != null) { for (int i = 0; i < virtualValues.size(); i++) { final View parent = virtualValues.keyAt(i); final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); parent.autofill(childrenValues); numApplied += childrenValues.size(); // TODO: we should provide a callback so the parent can call failures; something // like notifyAutofillFailed(View view, int[] childrenIds); } } mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied)); } } private LogMaker newLog(int category) { final LogMaker log = new LogMaker(category) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId); if (isCompatibilityModeEnabledLocked()) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); } final AutofillClient client = getClient(); if (client == null) { // Client should never be null here, but it doesn't hurt to check... log.setPackageName(mContext.getPackageName()); } else { log.setComponentName(client.autofillClientGetComponentName()); } return log; }
Set the tracked views.
Params:
  • trackedIds – The views to be tracked.
  • saveOnAllViewsInvisible – Finish the session once all tracked views are invisible.
  • saveOnFinish – Finish the session once the activity is finished.
  • fillableIds – Views that might anchor FillUI.
  • saveTriggerId – View that when clicked triggers commit().
/** * Set the tracked views. * * @param trackedIds The views to be tracked. * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. * @param saveOnFinish Finish the session once the activity is finished. * @param fillableIds Views that might anchor FillUI. * @param saveTriggerId View that when clicked triggers commit(). */
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, boolean saveOnAllViewsInvisible, boolean saveOnFinish, @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { synchronized (mLock) { if (mEnabled && mSessionId == sessionId) { if (saveOnAllViewsInvisible) { mTrackedViews = new TrackedViews(trackedIds); } else { mTrackedViews = null; } mSaveOnFinish = saveOnFinish; if (fillableIds != null) { if (mFillableIds == null) { mFillableIds = new ArraySet<>(fillableIds.length); } for (AutofillId id : fillableIds) { mFillableIds.add(id); } if (sVerbose) { Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds + ", mFillableIds" + mFillableIds); } } if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { // Turn off trigger on previous view id. setNotifyOnClickLocked(mSaveTriggerId, false); } if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { // Turn on trigger on new view id. mSaveTriggerId = saveTriggerId; setNotifyOnClickLocked(mSaveTriggerId, true); } } } } private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { final View view = findView(id); if (view == null) { Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); return; } view.setNotifyAutofillManagerOnClick(notify); } private void setSaveUiState(int sessionId, boolean shown) { if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); synchronized (mLock) { if (mSessionId != NO_SESSION) { // Race condition: app triggered a new session after the previous session was // finished but before server called setSaveUiState() - need to cancel the new // session to avoid further inconsistent behavior. Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown + ") called on existing session " + mSessionId + "; cancelling it"); cancelSessionLocked(); } if (shown) { mSessionId = sessionId; mState = STATE_SHOWING_SAVE_UI; } else { mSessionId = NO_SESSION; mState = STATE_UNKNOWN; } } }
Marks the state of the session as finished.
Params:
/** * Marks the state of the session as finished. * * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), * {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar * changed on compat mode), or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service * disabled further autofill requests for the activity). */
private void setSessionFinished(int newState) { synchronized (mLock) { if (sVerbose) { Log.v(TAG, "setSessionFinished(): from " + getStateAsStringLocked() + " to " + getStateAsString(newState)); } if (newState == STATE_UNKNOWN_COMPAT_MODE) { resetSessionLocked(/* resetEnteredIds= */ true); mState = STATE_UNKNOWN; } else { resetSessionLocked(/* resetEnteredIds= */ false); mState = newState; } } }
@hide
/** @hide */
public void requestHideFillUi() { requestHideFillUi(mIdShownFillUi, true); } private void requestHideFillUi(AutofillId id, boolean force) { final View anchor = id == null ? null : findView(id); if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor); if (anchor == null) { if (force) { // When user taps outside autofill window, force to close fill ui even id does // not match. AutofillClient client = getClient(); if (client != null) { client.autofillClientRequestHideFillUi(); } } return; } requestHideFillUi(id, anchor); } private void requestHideFillUi(AutofillId id, View anchor) { AutofillCallback callback = null; synchronized (mLock) { // We do not check the session id for two reasons: // 1. If local and remote session id are off sync the UI would be stuck shown // 2. There is a race between the user state being destroyed due the fill // service being uninstalled and the UI being dismissed. AutofillClient client = getClient(); if (client != null) { if (client.autofillClientRequestHideFillUi()) { mIdShownFillUi = null; callback = mCallback; } } } if (callback != null) { if (id.isVirtual()) { callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_HIDDEN); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); } } } private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { if (sVerbose) { Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id + ", sessionFinishedState=" + sessionFinishedState); } final View anchor = findView(id); if (anchor == null) { return; } AutofillCallback callback = null; synchronized (mLock) { if (mSessionId == sessionId && getClient() != null) { callback = mCallback; } } if (callback != null) { if (id.isVirtual()) { callback.onAutofillEvent(anchor, id.getVirtualChildId(), AutofillCallback.EVENT_INPUT_UNAVAILABLE); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); } } if (sessionFinishedState != 0) { // Callback call was "hijacked" to also update the session state. setSessionFinished(sessionFinishedState); } }
Find a single view by its id.
Params:
  • autofillId – The autofill id of the view
Returns:The view or null if view was not found
/** * Find a single view by its id. * * @param autofillId The autofill id of the view * * @return The view or {@code null} if view was not found */
private View findView(@NonNull AutofillId autofillId) { final AutofillClient client = getClient(); if (client != null) { return client.autofillClientFindViewByAutofillIdTraversal(autofillId); } return null; }
@hide
/** @hide */
public boolean hasAutofillFeature() { return mService != null; }
@hide
/** @hide */
public void onPendingSaveUi(int operation, IBinder token) { if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token); synchronized (mLock) { try { mService.onPendingSaveUi(operation, token); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } }
@hide
/** @hide */
public void dump(String outerPrefix, PrintWriter pw) { pw.print(outerPrefix); pw.println("AutofillManager:"); final String pfx = outerPrefix + " "; pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); pw.print(pfx); pw.print("context: "); pw.println(mContext); final AutofillClient client = getClient(); if (client != null) { pw.print(pfx); pw.print("client: "); pw.print(client); pw.print(" ("); pw.print(client.autofillClientGetActivityToken()); pw.println(')'); } pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); pw.print(pfx); pw.print("tracked views: "); if (mTrackedViews == null) { pw.println("null"); } else { final String pfx2 = pfx + " "; pw.println(); pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds); pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); } pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); pw.print(pfx); pw.print("entered ids: "); pw.println(mEnteredIds); pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); pw.print(pfx); pw.print("compat mode enabled: "); synchronized (mLock) { if (mCompatibilityBridge != null) { final String pfx2 = pfx + " "; pw.println("true"); pw.print(pfx2); pw.print("windowId: "); pw.println(mCompatibilityBridge.mFocusedWindowId); pw.print(pfx2); pw.print("nodeId: "); pw.println(mCompatibilityBridge.mFocusedNodeId); pw.print(pfx2); pw.print("virtualId: "); pw.println(AccessibilityNodeInfo .getVirtualDescendantId(mCompatibilityBridge.mFocusedNodeId)); pw.print(pfx2); pw.print("focusedBounds: "); pw.println(mCompatibilityBridge.mFocusedBounds); } else { pw.println("false"); } } pw.print(pfx); pw.print("debug: "); pw.print(sDebug); pw.print(" verbose: "); pw.println(sVerbose); } @GuardedBy("mLock") private String getStateAsStringLocked() { return getStateAsString(mState); } @NonNull private static String getStateAsString(int state) { switch (state) { case STATE_UNKNOWN: return "UNKNOWN"; case STATE_ACTIVE: return "ACTIVE"; case STATE_FINISHED: return "FINISHED"; case STATE_SHOWING_SAVE_UI: return "SHOWING_SAVE_UI"; case STATE_DISABLED_BY_SERVICE: return "DISABLED_BY_SERVICE"; case STATE_UNKNOWN_COMPAT_MODE: return "UNKNOWN_COMPAT_MODE"; default: return "INVALID:" + state; } } @GuardedBy("mLock") private boolean isActiveLocked() { return mState == STATE_ACTIVE; } @GuardedBy("mLock") private boolean isDisabledByServiceLocked() { return mState == STATE_DISABLED_BY_SERVICE; } @GuardedBy("mLock") private boolean isFinishedLocked() { return mState == STATE_FINISHED; } private void post(Runnable runnable) { final AutofillClient client = getClient(); if (client == null) { if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); return; } client.autofillClientRunOnUiThread(runnable); }
Implementation of the accessibility based compatibility.
/** * Implementation of the accessibility based compatibility. */
private final class CompatibilityBridge implements AccessibilityManager.AccessibilityPolicy { @GuardedBy("mLock") private final Rect mFocusedBounds = new Rect(); @GuardedBy("mLock") private final Rect mTempBounds = new Rect(); @GuardedBy("mLock") private int mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; @GuardedBy("mLock") private long mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; // Need to report a fake service in case a11y clients check the service list @NonNull @GuardedBy("mLock") AccessibilityServiceInfo mCompatServiceInfo; CompatibilityBridge() { final AccessibilityManager am = AccessibilityManager.getInstance(mContext); am.setAccessibilityPolicy(this); } private AccessibilityServiceInfo getCompatServiceInfo() { synchronized (mLock) { if (mCompatServiceInfo != null) { return mCompatServiceInfo; } final Intent intent = new Intent(); intent.setComponent(new ComponentName("android", "com.android.server.autofill.AutofillCompatAccessibilityService")); final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService( intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); try { mCompatServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); } catch (XmlPullParserException | IOException e) { Log.e(TAG, "Cannot find compat autofill service:" + intent); throw new IllegalStateException("Cannot find compat autofill service"); } return mCompatServiceInfo; } } @Override public boolean isEnabled(boolean accessibilityEnabled) { return true; } @Override public int getRelevantEventTypes(int relevantEventTypes) { return relevantEventTypes | AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED | AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; } @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( List<AccessibilityServiceInfo> installedServices) { if (installedServices == null) { installedServices = new ArrayList<>(); } installedServices.add(getCompatServiceInfo()); return installedServices; } @Override public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( int feedbackTypeFlags, List<AccessibilityServiceInfo> enabledService) { if (enabledService == null) { enabledService = new ArrayList<>(); } enabledService.add(getCompatServiceInfo()); return enabledService; } @Override public AccessibilityEvent onAccessibilityEvent(AccessibilityEvent event, boolean accessibilityEnabled, int relevantEventTypes) { final int type = event.getEventType(); if (sVerbose) { // NOTE: this is waaay spammy, but that's life. Log.v(TAG, "onAccessibilityEvent(" + AccessibilityEvent.eventTypeToString(type) + "): virtualId=" + AccessibilityNodeInfo.getVirtualDescendantId(event.getSourceNodeId()) + ", client=" + getClient()); } switch (type) { case AccessibilityEvent.TYPE_VIEW_FOCUSED: { synchronized (mLock) { if (mFocusedWindowId == event.getWindowId() && mFocusedNodeId == event.getSourceNodeId()) { return event; } if (mFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID && mFocusedNodeId != AccessibilityNodeInfo.UNDEFINED_NODE_ID) { notifyViewExited(mFocusedWindowId, mFocusedNodeId); mFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; mFocusedNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; mFocusedBounds.set(0, 0, 0, 0); } final int windowId = event.getWindowId(); final long nodeId = event.getSourceNodeId(); if (notifyViewEntered(windowId, nodeId, mFocusedBounds)) { mFocusedWindowId = windowId; mFocusedNodeId = nodeId; } } } break; case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: { synchronized (mLock) { if (mFocusedWindowId == event.getWindowId() && mFocusedNodeId == event.getSourceNodeId()) { notifyValueChanged(event.getWindowId(), event.getSourceNodeId()); } } } break; case AccessibilityEvent.TYPE_VIEW_CLICKED: { synchronized (mLock) { notifyViewClicked(event.getWindowId(), event.getSourceNodeId()); } } break; case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { final AutofillClient client = getClient(); if (client != null) { synchronized (mLock) { if (client.autofillClientIsFillUiShowing()) { notifyViewEntered(mFocusedWindowId, mFocusedNodeId, mFocusedBounds); } updateTrackedViewsLocked(); } } } break; } return accessibilityEnabled ? event : null; } private boolean notifyViewEntered(int windowId, long nodeId, Rect focusedBounds) { final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); if (!isVirtualNode(virtualId)) { return false; } final View view = findViewByAccessibilityId(windowId, nodeId); if (view == null) { return false; } final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); if (node == null) { return false; } if (!node.isEditable()) { return false; } final Rect newBounds = mTempBounds; node.getBoundsInScreen(newBounds); if (newBounds.equals(focusedBounds)) { return false; } focusedBounds.set(newBounds); AutofillManager.this.notifyViewEntered(view, virtualId, newBounds); return true; } private void notifyViewExited(int windowId, long nodeId) { final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); if (!isVirtualNode(virtualId)) { return; } final View view = findViewByAccessibilityId(windowId, nodeId); if (view == null) { return; } AutofillManager.this.notifyViewExited(view, virtualId); } private void notifyValueChanged(int windowId, long nodeId) { final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); if (!isVirtualNode(virtualId)) { return; } final View view = findViewByAccessibilityId(windowId, nodeId); if (view == null) { return; } final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); if (node == null) { return; } AutofillManager.this.notifyValueChanged(view, virtualId, AutofillValue.forText(node.getText())); } private void notifyViewClicked(int windowId, long nodeId) { final int virtualId = AccessibilityNodeInfo.getVirtualDescendantId(nodeId); if (!isVirtualNode(virtualId)) { return; } final View view = findViewByAccessibilityId(windowId, nodeId); if (view == null) { return; } final AccessibilityNodeInfo node = findVirtualNodeByAccessibilityId(view, virtualId); if (node == null) { return; } AutofillManager.this.notifyViewClicked(view, virtualId); } @GuardedBy("mLock") private void updateTrackedViewsLocked() { if (mTrackedViews != null) { mTrackedViews.onVisibleForAutofillChangedLocked(); } } private View findViewByAccessibilityId(int windowId, long nodeId) { final AutofillClient client = getClient(); if (client == null) { return null; } final int viewId = AccessibilityNodeInfo.getAccessibilityViewId(nodeId); return client.autofillClientFindViewByAccessibilityIdTraversal(viewId, windowId); } private AccessibilityNodeInfo findVirtualNodeByAccessibilityId(View view, int virtualId) { final AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); if (provider == null) { return null; } return provider.createAccessibilityNodeInfo(virtualId); } private boolean isVirtualNode(int nodeId) { return nodeId != AccessibilityNodeProvider.HOST_VIEW_ID && nodeId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; } }
View tracking information. Once all tracked views become invisible the session is finished.
/** * View tracking information. Once all tracked views become invisible the session is finished. */
private class TrackedViews {
Visible tracked views
/** Visible tracked views */
@Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
Invisible tracked views
/** Invisible tracked views */
@Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
Check if set is null or value is in set.
Params:
  • set – The set or null (== empty set)
  • value – The value that might be in the set
Returns:true iff set is not empty and value is in set
/** * Check if set is null or value is in set. * * @param set The set or null (== empty set) * @param value The value that might be in the set * * @return {@code true} iff set is not empty and value is in set */
// TODO: move to Helper as static method private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) { return set != null && set.contains(value); }
Add a value to a set. If set is null, create a new set.
Params:
  • set – The set or null (== empty set)
  • valueToAdd – The value to add
Returns:The set including the new value. If set was null, a set containing only the new value.
/** * Add a value to a set. If set is null, create a new set. * * @param set The set or null (== empty set) * @param valueToAdd The value to add * * @return The set including the new value. If set was {@code null}, a set containing only * the new value. */
// TODO: move to Helper as static method @NonNull private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) { if (set == null) { set = new ArraySet<>(1); } set.add(valueToAdd); return set; }
Remove a value from a set.
Params:
  • set – The set or null (== empty set)
  • valueToRemove – The value to remove
Returns:The set without the removed value. null if set was null, or is empty after removal.
/** * Remove a value from a set. * * @param set The set or null (== empty set) * @param valueToRemove The value to remove * * @return The set without the removed value. {@code null} if set was null, or is empty * after removal. */
// TODO: move to Helper as static method @Nullable private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) { if (set == null) { return null; } set.remove(valueToRemove); if (set.isEmpty()) { return null; } return set; }
Set the tracked views.
Params:
  • trackedIds – The views to be tracked
/** * Set the tracked views. * * @param trackedIds The views to be tracked */
TrackedViews(@Nullable AutofillId[] trackedIds) { final AutofillClient client = getClient(); if (!ArrayUtils.isEmpty(trackedIds) && client != null) { final boolean[] isVisible; if (client.autofillClientIsVisibleForAutofill()) { if (sVerbose) Log.v(TAG, "client is visible, check tracked ids"); isVisible = client.autofillClientGetViewVisibility(trackedIds); } else { // All false isVisible = new boolean[trackedIds.length]; } final int numIds = trackedIds.length; for (int i = 0; i < numIds; i++) { final AutofillId id = trackedIds[i]; if (isVisible[i]) { mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); } else { mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); } } } if (sVerbose) { Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): " + " mVisibleTrackedIds=" + mVisibleTrackedIds + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); } if (mVisibleTrackedIds == null) { finishSessionLocked(); } }
Called when a view's visibility changes.
Params:
  • id – the id of the view/virtual view whose visibility changed.
  • isVisible – visible if the view is visible in the view hierarchy.
/** * Called when a {@link View view's} visibility changes. * * @param id the id of the view/virtual view whose visibility changed. * @param isVisible visible if the view is visible in the view hierarchy. */
@GuardedBy("mLock") void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { if (sDebug) { Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible=" + isVisible); } if (isClientVisibleForAutofillLocked()) { if (isVisible) { if (isInSet(mInvisibleTrackedIds, id)) { mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); } } else { if (isInSet(mVisibleTrackedIds, id)) { mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); } } } if (mVisibleTrackedIds == null) { if (sVerbose) { Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds); } finishSessionLocked(); } }
Called once the client becomes visible.
See Also:
  • autofillClientIsVisibleForAutofill.autofillClientIsVisibleForAutofill()
/** * Called once the client becomes visible. * * @see AutofillClient#autofillClientIsVisibleForAutofill() */
@GuardedBy("mLock") void onVisibleForAutofillChangedLocked() { // The visibility of the views might have changed while the client was not be visible, // hence update the visibility state for all views. AutofillClient client = getClient(); ArraySet<AutofillId> updatedVisibleTrackedIds = null; ArraySet<AutofillId> updatedInvisibleTrackedIds = null; if (client != null) { if (sVerbose) { Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds + " vis=" + mVisibleTrackedIds); } if (mInvisibleTrackedIds != null) { final ArrayList<AutofillId> orderedInvisibleIds = new ArrayList<>(mInvisibleTrackedIds); final boolean[] isVisible = client.autofillClientGetViewVisibility( Helper.toArray(orderedInvisibleIds)); final int numInvisibleTrackedIds = orderedInvisibleIds.size(); for (int i = 0; i < numInvisibleTrackedIds; i++) { final AutofillId id = orderedInvisibleIds.get(i); if (isVisible[i]) { updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); if (sDebug) { Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); } } else { updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); } } } if (mVisibleTrackedIds != null) { final ArrayList<AutofillId> orderedVisibleIds = new ArrayList<>(mVisibleTrackedIds); final boolean[] isVisible = client.autofillClientGetViewVisibility( Helper.toArray(orderedVisibleIds)); final int numVisibleTrackedIds = orderedVisibleIds.size(); for (int i = 0; i < numVisibleTrackedIds; i++) { final AutofillId id = orderedVisibleIds.get(i); if (isVisible[i]) { updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); } else { updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); if (sDebug) { Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); } } } } mInvisibleTrackedIds = updatedInvisibleTrackedIds; mVisibleTrackedIds = updatedVisibleTrackedIds; } if (mVisibleTrackedIds == null) { if (sVerbose) { Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); } finishSessionLocked(); } } }
Callback for autofill related events.

Typically used for applications that display their own "auto-complete" views, so they can enable / disable such views when the autofill UI is shown / hidden.

/** * Callback for autofill related events. * * <p>Typically used for applications that display their own "auto-complete" views, so they can * enable / disable such views when the autofill UI is shown / hidden. */
public abstract static class AutofillCallback {
@hide
/** @hide */
@IntDef(prefix = { "EVENT_INPUT_" }, value = { EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE }) @Retention(RetentionPolicy.SOURCE) public @interface AutofillEventType {}
The autofill input UI associated with the view was shown.

If the view provides its own auto-complete UI and its currently shown, it should be hidden upon receiving this event.

/** * The autofill input UI associated with the view was shown. * * <p>If the view provides its own auto-complete UI and its currently shown, it * should be hidden upon receiving this event. */
public static final int EVENT_INPUT_SHOWN = 1;
The autofill input UI associated with the view was hidden.

If the view provides its own auto-complete UI that was hidden upon a EVENT_INPUT_SHOWN event, it could be shown again now.

/** * The autofill input UI associated with the view was hidden. * * <p>If the view provides its own auto-complete UI that was hidden upon a * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. */
public static final int EVENT_INPUT_HIDDEN = 2;
The autofill input UI associated with the view isn't shown because autofill is not available.

If the view provides its own auto-complete UI but was not displaying it to avoid flickering, it could shown it upon receiving this event.

/** * The autofill input UI associated with the view isn't shown because * autofill is not available. * * <p>If the view provides its own auto-complete UI but was not displaying it * to avoid flickering, it could shown it upon receiving this event. */
public static final int EVENT_INPUT_UNAVAILABLE = 3;
Called after a change in the autofill state associated with a view.
Params:
/** * Called after a change in the autofill state associated with a view. * * @param view view associated with the change. * * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. */
public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) { }
Called after a change in the autofill state associated with a virtual view.
Params:
  • view – parent view associated with the change.
  • virtualId – id identifying the virtual child inside the parent view.
  • event – currently either EVENT_INPUT_SHOWN or EVENT_INPUT_HIDDEN.
/** * Called after a change in the autofill state associated with a virtual view. * * @param view parent view associated with the change. * @param virtualId id identifying the virtual child inside the parent view. * * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}. */
public void onAutofillEvent(@NonNull View view, int virtualId, @AutofillEventType int event) { } } private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub { private final WeakReference<AutofillManager> mAfm; AutofillManagerClient(AutofillManager autofillManager) { mAfm = new WeakReference<>(autofillManager); } @Override public void setState(int flags) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.setState(flags)); } } @Override public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.autofill(sessionId, ids, values)); } } @Override public void authenticate(int sessionId, int authenticationId, IntentSender intent, Intent fillInIntent) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent)); } } @Override public void requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds, presenter)); } } @Override public void requestHideFillUi(int sessionId, AutofillId id) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.requestHideFillUi(id, false)); } } @Override public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); } } @Override public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.dispatchUnhandledKey(sessionId, id, fullScreen)); } } @Override public void startIntentSender(IntentSender intentSender, Intent intent) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> { try { afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); } }); } } @Override public void setTrackedViews(int sessionId, AutofillId[] ids, boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, AutofillId saveTriggerId) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, saveOnFinish, fillableIds, saveTriggerId)); } } @Override public void setSaveUiState(int sessionId, boolean shown) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.setSaveUiState(sessionId, shown)); } } @Override public void setSessionFinished(int newState) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.post(() -> afm.setSessionFinished(newState)); } } } }