Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e09bca5 authored by Simranjit Kohli's avatar Simranjit Kohli
Browse files

[Relayout] Part 4: Implement core logic

Re-attempt when autofill fails

Bug: 238252288
Flag: EXEMPT : DeviceConfig flags used: enable_relayout
exception granted in b/318391032
Test: atest CtsAutoFillServiceTestCases
      atest FrameworksCoreTests:AutofillStateFingerprint

Change-Id: I302584adbd6da338019663203941b5bc2173b7e7
parent 8b4f51de
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -582,4 +582,9 @@ public final class AutofillClientController implements AutofillManager.AutofillC
            Log.e(TAG, "authenticate() failed for intent:" + intent, e);
        }
    }

    @Override
    public boolean isActivityResumed() {
        return mActivity.isResumed();
    }
}
+124 −23
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -774,6 +775,13 @@ public final class AutofillManager {
    // dataset in responses. Used to avoid request pre-fill request again and again.
    private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();

    // Whether we need to re-attempt fill again. Needed for case of relayout.
    private boolean mFillReAttemptNeeded = false;

    private Map<Integer, AutofillId> mFingerprintToViewMap = new ArrayMap<>();

    private AutofillStateFingerprint mAutofillStateFingerprint;

    /** @hide */
    public interface AutofillClient {
        /**
@@ -909,6 +917,11 @@ public final class AutofillManager {
         * @return An ID that is unique in the activity.
         */
        @Nullable AutofillId autofillClientGetNextAutofillId();

        /**
         * @return Whether the activity is resumed or not.
         */
        boolean isActivityResumed();
    }

    /**
@@ -919,6 +932,7 @@ public final class AutofillManager {
        mService = service;
        mOptions = context.getAutofillOptions();
        mIsFillRequested = new AtomicBoolean(false);
        mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();

        mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
        mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
@@ -1357,6 +1371,12 @@ public final class AutofillManager {
            mOnInvisibleCalled = true;

            if (isExpiredResponse) {
                if (mRelayoutFix && isAuthenticationPending()) {
                    Log.i(TAG, "onInvisibleForAutofill(): Ignoring expiringResponse due to pending"
                            + " authentication");
                    return;
                }
                Log.i(TAG, "onInvisibleForAutofill(): expiringResponse");
                // Notify service the response has expired.
                updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
                        ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
@@ -1519,8 +1539,9 @@ public final class AutofillManager {
     * @hide
     */
    public boolean shouldRetryFill() {
        // TODO: Implement in follow-up cl
        return false;
        synchronized (mLock) {
            return isAuthenticationPending() && mFillReAttemptNeeded;
        }
    }

    /**
@@ -1531,8 +1552,13 @@ public final class AutofillManager {
     */
    public boolean attemptRefill() {
        Log.i(TAG, "Attempting refill");
        // TODO: Implement in follow-up cl
        return false;
        // Find active autofillable views. Compute their fingerprints
        List<View> autofillableViews =
                getClient().autofillClientFindAutofillableViewsByTraversal();
        if (sDebug) {
            Log.d(TAG, "Autofillable views count:" + autofillableViews.size());
        }
        return mAutofillStateFingerprint.attemptRefill(autofillableViews, this);
    }

    /**
@@ -2493,7 +2519,13 @@ public final class AutofillManager {

    /** @hide */
    public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
        if (sVerbose) {
            Log.v(TAG, "onAuthenticationResult(): authId= " + authenticationId + ", data=" + data);
        }
        if (!hasAutofillFeature()) {
            if (sVerbose) {
                Log.v(TAG, "onAuthenticationResult(): autofill not enabled");
            }
            return;
        }
        // TODO: the result code is being ignored, so this method is not reliably
@@ -2501,10 +2533,6 @@ public final class AutofillManager {
        // 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(): id= " + authenticationId + ", data=" + data);
        }

        synchronized (mLock) {
            if (!isActiveLocked()) {
                Log.w(TAG, "onAuthenticationResult(): sessionId=" + mSessionId + " not active");
@@ -2661,6 +2689,7 @@ public final class AutofillManager {
            mSessionId = receiver.getIntResult();
            if (mSessionId != NO_SESSION) {
                mState = STATE_ACTIVE;
                mAutofillStateFingerprint.setSessionId(mSessionId);
            }
            final int extraFlags = receiver.getOptionalExtraIntResult(0);
            if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
@@ -2722,6 +2751,9 @@ public final class AutofillManager {
        if (resetEnteredIds) {
            mEnteredIds = null;
        }
        mFillReAttemptNeeded = false;
        mFingerprintToViewMap.clear();
        mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();
    }

    @GuardedBy("mLock")
@@ -2984,8 +3016,12 @@ public final class AutofillManager {
            Intent fillInIntent, boolean authenticateInline) {
        synchronized (mLock) {
            if (sessionId == mSessionId) {
                if (mRelayoutFixDeprecated) {
                if (mRelayoutFixDeprecated || mRelayoutFix) {
                    mState = STATE_PENDING_AUTHENTICATION;
                    if (sVerbose) {
                        Log.v(TAG, "entering STATE_PENDING_AUTHENTICATION : mRelayoutFix:"
                                + mRelayoutFix);
                    }
                }
                final AutofillClient client = getClient();
                if (client != null) {
@@ -3191,9 +3227,28 @@ public final class AutofillManager {

    @GuardedBy("mLock")
    private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) {
        handleFailedIdsLocked(failedIds, null, false, false);
    }

    @GuardedBy("mLock")
    private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds,
            ArrayList<AutofillValue> failedAutofillValues, boolean hideHighlight,
            boolean isRefill) {
        if (!failedIds.isEmpty() && sVerbose) {
            Log.v(TAG, "autofill(): total failed views: " + failedIds);
        }

        if (mRelayoutFix && !failedIds.isEmpty()) {
            // Activity isn't in resumed state, so it's very possible that relayout could've
            // occurred, so wait for it to declare proper failure. It's a temporary failure at the
            // moment. We'll try again later when the activity is resumed.

            // The above doesn't seem to be the correct way. Look for pending auth cases.
            // TODO(b/238252288): Check whether there was any auth done at all
            mFillReAttemptNeeded = true;
            mAutofillStateFingerprint.storeFailedIdsAndValues(
                    failedIds, failedAutofillValues, hideHighlight);
        }
        try {
            mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
        } catch (RemoteException e) {
@@ -3202,6 +3257,25 @@ public final class AutofillManager {
            // a consequence of something going wrong on the server side...
            throw e.rethrowFromSystemServer();
        }
        if (mRelayoutFix && !failedIds.isEmpty()) {
            if (!getClient().isActivityResumed()) {
                if (sVerbose) {
                    Log.v(TAG, "handleFailedIdsLocked(): failed id's exist, but activity not"
                            + " resumed");
                }
            } else {
                if (isRefill) {
                    Log.i(TAG, "handleFailedIdsLocked(): Attempted refill, but failed");
                } else {
                    // activity has been resumed, try to re-fill
                    // getClient().isActivityResumed() && !failedIds.isEmpty() && !isRefill
                    // TODO(b/238252288): Do better state management, and only trigger the following
                    //  if there was auth previously.
                    Log.i(TAG, "handleFailedIdsLocked(): Attempting refill");
                    attemptRefill();
                }
            }
        }
    }

    private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
@@ -3216,13 +3290,30 @@ public final class AutofillManager {
                return;
            }

            final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
                    Helper.toArray(ids));

            autofill(views, ids, values, hideHighlight, false);
        }
    }

    void autofill(View[] views, List<AutofillId> ids, List<AutofillValue> values,
            boolean hideHighlight, boolean isRefill) {
        if (sVerbose) {
            Log.v(TAG, "autofill() ids:" + ids + " isRefill:" + isRefill);
        }
        synchronized (mLock) {
            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 = new ArrayList<>();
            ArrayList<AutofillValue> failedAutofillValues = new ArrayList<>();

            if (mLastAutofilledData == null) {
                mLastAutofilledData = new ParcelableMap(itemCount);
@@ -3237,7 +3328,9 @@ public final class AutofillManager {
                    // 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);
                    // Possible relayout scenario
                    failedIds.add(id);
                    failedAutofillValues.add(value);
                    continue;
                }
                // Mark the view as to be autofilled with 'value'
@@ -3268,7 +3361,7 @@ public final class AutofillManager {
                }
            }

            handleFailedIdsLocked(failedIds);
            handleFailedIdsLocked(failedIds, failedAutofillValues, hideHighlight, isRefill);

            if (virtualValues != null) {
                for (int i = 0; i < virtualValues.size(); i++) {
@@ -3359,10 +3452,12 @@ public final class AutofillManager {
     */
    private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
            @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
            @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId,
            boolean shouldGrabViewFingerprints) {
        if (saveTriggerId != null) {
            saveTriggerId.resetSessionId();
        }
        final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
        synchronized (mLock) {
            if (sVerbose) {
                Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId
@@ -3372,6 +3467,7 @@ public final class AutofillManager {
                        + ", fillableIds=" + Arrays.toString(fillableIds)
                        + ", saveTrigerId=" + saveTriggerId
                        + ", mFillableIds=" + mFillableIds
                        + ", shouldGrabViewFingerprints=" + shouldGrabViewFingerprints
                        + ", mEnabled=" + mEnabled
                        + ", mSessionId=" + mSessionId);
            }
@@ -3405,7 +3501,6 @@ public final class AutofillManager {
                    trackedIds = null;
                }

                final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
                if (mFillableIds != null) {
                    allFillableIds.addAll(mFillableIds);
                }
@@ -3424,6 +3519,12 @@ public final class AutofillManager {
                    mTrackedViews = null;
                }
            }
            if (mRelayoutFix && shouldGrabViewFingerprints) {
                // For all the views: tracked and others, calculate fingerprints and store them.
                mAutofillStateFingerprint.setUseRelativePosition(mRelativePositionForRelayout);
                mAutofillStateFingerprint.storeStatePriorToAuthentication(
                        getClient(), allFillableIds);
            }
        }
    }

@@ -3845,7 +3946,7 @@ public final class AutofillManager {

    @GuardedBy("mLock")
    private boolean isPendingAuthenticationLocked() {
        return mRelayoutFixDeprecated && mState == STATE_PENDING_AUTHENTICATION;
        return (mRelayoutFixDeprecated || mRelayoutFix) && mState == STATE_PENDING_AUTHENTICATION;
    }

    @GuardedBy("mLock")
@@ -3858,7 +3959,7 @@ public final class AutofillManager {
        return mState == STATE_FINISHED;
    }

    private void post(Runnable runnable) {
    void post(Runnable runnable) {
        final AutofillClient client = getClient();
        if (client == null) {
            if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
@@ -4700,11 +4801,11 @@ public final class AutofillManager {
        @Override
        public void setTrackedViews(int sessionId, AutofillId[] ids,
                boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
                AutofillId saveTriggerId) {
                AutofillId saveTriggerId, boolean shouldGrabViewFingerprints) {
            final AutofillManager afm = mAfm.get();
            if (afm != null) {
                afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
                        saveOnFinish, fillableIds, saveTriggerId));
                        saveOnFinish, fillableIds, saveTriggerId, shouldGrabViewFingerprints));
            }
        }

+292 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.view.autofill.Helper.sDebug;

import android.annotation.NonNull;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.view.View;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * This class manages and stores the autofillable views fingerprints for use in relayout situations.
 * @hide
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class AutofillStateFingerprint {

    ArrayList<AutofillId> mPriorAutofillIds;
    ArrayList<Integer> mViewHashCodes; // each entry corresponding to mPriorAutofillIds .

    boolean mHideHighlight = false;

    private int mSessionId;

    Map<Integer, AutofillId> mHashToAutofillIdMap = new ArrayMap<>();
    Map<AutofillId, AutofillId> mOldIdsToCurrentAutofillIdMap = new ArrayMap<>();

    // These failed id's are attempted to be refilled again after relayout.
    private ArrayList<AutofillId> mFailedIds = new ArrayList<>();
    private ArrayList<AutofillValue> mFailedAutofillValues = new ArrayList<>();

    // whether to use relative positions for computing hashes.
    private boolean mUseRelativePosition;

    private static final String TAG = "AutofillStateFingerprint";

    static AutofillStateFingerprint createInstance() {
        return new AutofillStateFingerprint();
    }

    private AutofillStateFingerprint() {
    }

    /**
     * Set sessionId for the instance
     */
    void setSessionId(int sessionId) {
        mSessionId = sessionId;
    }

    /**
     * Sets whether relative position of the views should be used to calculate fingerprints.
     */
    void setUseRelativePosition(boolean useRelativePosition) {
        mUseRelativePosition = useRelativePosition;
    }

    /**
     * Store the state of the views prior to the authentication.
     */
    void storeStatePriorToAuthentication(
            AutofillManager.AutofillClient client, Set<AutofillId> autofillIds) {
        if (mUseRelativePosition) {
            List<View> autofillableViews = client.autofillClientFindAutofillableViewsByTraversal();
            if (sDebug) {
                Log.d(TAG, "Autofillable views count prior to auth:" + autofillableViews.size());
            }
            ArrayList<Integer> hashes = getFingerprintIds(autofillableViews);
            for (int i = 0; i < hashes.size(); i++) {
                View view = autofillableViews.get(i);
                if (view != null) {
                    mHashToAutofillIdMap.put(hashes.get(i), view.getAutofillId());
                } else {
                    if (sDebug) {
                        Log.d(TAG, "Encountered null view");
                    }
                }
            }
        } else {
            // Just use the provided autofillIds and get their hashes
            if (sDebug) {
                Log.d(TAG, "Size of autofillId's being stored: " + autofillIds.size()
                        + " list:" + autofillIds);
            }
            AutofillId[] autofillIdsArr = Helper.toArray(autofillIds);
            View[] views = client.autofillClientFindViewsByAutofillIdTraversal(autofillIdsArr);
            for (int i = 0; i < autofillIdsArr.length; i++) {
                View view = views[i];
                if (view != null) {
                    int id = getEphemeralFingerprintId(view);
                    AutofillId autofillId = view.getAutofillId();
                    autofillId.setSessionId(mSessionId);
                    mHashToAutofillIdMap.put(id, autofillId);
                } else {
                    if (sDebug) {
                        Log.d(TAG, "Encountered null view");
                    }
                }
            }
        }
    }

    /**
     * Store failed ids, so that they can be refilled later
     */
    void storeFailedIdsAndValues(
            @NonNull ArrayList<AutofillId> failedIds,
            ArrayList<AutofillValue> failedAutofillValues,
            boolean hideHighlight) {
        for (AutofillId failedId : failedIds) {
            if (failedId != null) {
                failedId.setSessionId(mSessionId);
            } else {
                if (sDebug) {
                    Log.d(TAG, "Got null failed ids");
                }
            }
        }
        mFailedIds = failedIds;
        mFailedAutofillValues = failedAutofillValues;
        mHideHighlight = hideHighlight;
    }

    private void dumpCurrentState() {
        Log.d(TAG, "FailedId's: " + mFailedIds);
        Log.d(TAG, "Hashes from map" + mHashToAutofillIdMap);
    }

    boolean attemptRefill(
            List<View> currentAutofillableViews, @NonNull AutofillManager autofillManager) {
        if (sDebug) {
            dumpCurrentState();
        }
        // For the autofillable views, compute their hashes
        ArrayList<Integer> currentHashes = getFingerprintIds(currentAutofillableViews);

        // For the computed hashes, try to look for the old fingerprints.
        // If match found, update the new autofill ids of those views
        Map<AutofillId, View> oldFailedIdsToCurrentViewMap = new HashMap<>();
        for (int i = 0; i < currentAutofillableViews.size(); i++) {
            View view = currentAutofillableViews.get(i);
            int currentHash = currentHashes.get(i);
            AutofillId currentAutofillId = view.getAutofillId();
            currentAutofillId.setSessionId(mSessionId);
            if (mHashToAutofillIdMap.containsKey(currentHash)) {
                AutofillId oldAutofillId = mHashToAutofillIdMap.get(currentHash);
                oldAutofillId.setSessionId(mSessionId);
                mOldIdsToCurrentAutofillIdMap.put(oldAutofillId, currentAutofillId);
                Log.i(TAG, "Mapping current autofill id: " + view.getAutofillId()
                        + " to existing autofill id " + oldAutofillId);

                oldFailedIdsToCurrentViewMap.put(oldAutofillId, view);
            } else {
                Log.i(TAG, "Couldn't map current autofill id: " + view.getAutofillId()
                        + " with currentHash:" + currentHash + " for view:" + view);
            }
        }

        int viewsCount = 0;
        View[] views = new View[mFailedIds.size()];
        for (int i = 0; i < mFailedIds.size(); i++) {
            AutofillId oldAutofillId = mFailedIds.get(i);
            AutofillId currentAutofillId = mOldIdsToCurrentAutofillIdMap.get(oldAutofillId);
            if (currentAutofillId == null) {
                if (sDebug) {
                    Log.d(TAG, "currentAutofillId = null");
                }
            }
            mFailedIds.set(i, currentAutofillId);
            views[i] = oldFailedIdsToCurrentViewMap.get(oldAutofillId);
            if (views[i] != null) {
                viewsCount++;
            }
        }

        if (sDebug) {
            dumpCurrentState();
        }

        // Attempt autofill now
        Slog.i(TAG, "Attempting refill of views. Found " + viewsCount
                + " views to refill from previously " + mFailedIds.size()
                + " failed ids:" + mFailedIds);
        autofillManager.post(
                () -> autofillManager.autofill(
                        views, mFailedIds, mFailedAutofillValues, mHideHighlight,
                        true /* isRefill */));

        return false;
    }

    /**
     * Retrieves fingerprint hashes for the views
     */
    ArrayList<Integer> getFingerprintIds(@NonNull List<View> views) {
        ArrayList<Integer> ids = new ArrayList(views.size());
        for (int i = 0; i < views.size(); i++) {
            ids.add(getEphemeralFingerprintId(views.get(i)));
        }
        return ids;
    }

    /**
     * Returns fingerprint hash for the view.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public static int getEphemeralFingerprintId(View v) {
        if (v == null) return -1;
        int inputType = Integer.MIN_VALUE;
        int imeOptions = Integer.MIN_VALUE;
        boolean isSingleLine = false;
        CharSequence hints = "";
        if (v instanceof TextView) {
            TextView tv = (TextView) v;
            inputType = tv.getInputType();
            hints = tv.getHint();
            isSingleLine = tv.isSingleLine();
            imeOptions = tv.getImeOptions();
            // TODO(b/238252288): Consider adding more IME related fields.
        }
        CharSequence contentDesc = v.getContentDescription();
        CharSequence tooltip = v.getTooltipText();

        int autofillType = v.getAutofillType();
        String[] autofillHints = v.getAutofillHints();
        int visibility = v.getVisibility();

        int paddingLeft = v.getPaddingLeft();
        int paddingRight = v.getPaddingRight();
        int paddingTop = v.getPaddingTop();
        int paddingBottom = v.getPaddingBottom();

        // TODO(b/238252288): Following are making relayout flaky. Do more analysis to figure out
        //  why.
        int height = v.getHeight();
        int width = v.getWidth();

        // Order doesn't matter much here. We can change the order, as long as we use the same
        // order for storing and fetching fingerprints. The order can be changed in platform
        // versions.
        int hash = Objects.hash(visibility, inputType, imeOptions, isSingleLine, hints,
                contentDesc, tooltip, autofillType, Arrays.deepHashCode(autofillHints),
                paddingBottom, paddingTop, paddingRight, paddingLeft);
        if (sDebug) {
            Log.d(TAG, "Hash: " + hash + " for AutofillId:" + v.getAutofillId()
                    + " visibility:" + visibility
                    + " inputType:" + inputType
                    + " imeOptions:" + imeOptions
                    + " isSingleLine:" + isSingleLine
                    + " hints:" + hints
                    + " contentDesc:" + contentDesc
                    + " tooltipText:" + tooltip
                    + " autofillType:" + autofillType
                    + " autofillHints:" + Arrays.toString(autofillHints)
                    + " height:" + height
                    + " width:" + width
                    + " paddingLeft:" + paddingLeft
                    + " paddingRight:" + paddingRight
                    + " paddingTop:" + paddingTop
                    + " paddingBottom:" + paddingBottom
            );
        }
        return hash;
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -72,7 +72,8 @@ oneway interface IAutoFillManagerClient {
      */
    void setTrackedViews(int sessionId, in @nullable AutofillId[] savableIds,
            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
            in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId);
            in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId,
            in boolean shouldGrabViewFingerprints);

    /**
     * Requests showing the fill UI.
+152 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading