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

Commit 8f502801 authored by TYM Tsai's avatar TYM Tsai
Browse files

Make TextView do not show IME when fill dialog popup

Make to show fill dialog only if IME is not showing. And if the field
will show fill dialog, ignore to show ime when clicks on the field.

Bug: 210926084
Test: Manual
  launch sample and click on password field -> show fill dialog
  click on username field (no fill dialog) -> show fill UI and IME
  IME is showing and click on password field -> show fill UI
Test: atest android.autofillservice.cts.dialog.LoginActivityTest
Test: atest android.widget.cts.EditTextTest
Change-Id: I6080510d87c50969d85fd9ed746ceba7e3c1c685
parent 3c5b9738
Loading
Loading
Loading
Loading
+16 −5
Original line number Diff line number Diff line
@@ -105,6 +105,12 @@ public final class FillRequest implements Parcelable {
     */
    public static final @RequestFlags int FLAG_SUPPORTS_FILL_DIALOG = 0x40;

    /**
     * Indicates the ime is showing while request coming.
     * @hide
     */
    public static final @RequestFlags int FLAG_IME_SHOWING = 0x80;

    /** @hide */
    public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;

@@ -201,7 +207,8 @@ public final class FillRequest implements Parcelable {
        FLAG_COMPATIBILITY_MODE_REQUEST,
        FLAG_PASSWORD_INPUT_TYPE,
        FLAG_VIEW_NOT_FOCUSED,
        FLAG_SUPPORTS_FILL_DIALOG
        FLAG_SUPPORTS_FILL_DIALOG,
        FLAG_IME_SHOWING
    })
    @Retention(RetentionPolicy.SOURCE)
    @DataClass.Generated.Member
@@ -227,6 +234,8 @@ public final class FillRequest implements Parcelable {
                    return "FLAG_VIEW_NOT_FOCUSED";
            case FLAG_SUPPORTS_FILL_DIALOG:
                    return "FLAG_SUPPORTS_FILL_DIALOG";
            case FLAG_IME_SHOWING:
                    return "FLAG_IME_SHOWING";
            default: return Integer.toHexString(value);
        }
    }
@@ -302,7 +311,8 @@ public final class FillRequest implements Parcelable {
                        | FLAG_COMPATIBILITY_MODE_REQUEST
                        | FLAG_PASSWORD_INPUT_TYPE
                        | FLAG_VIEW_NOT_FOCUSED
                        | FLAG_SUPPORTS_FILL_DIALOG);
                        | FLAG_SUPPORTS_FILL_DIALOG
                        | FLAG_IME_SHOWING);
        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
        this.mDelayedFillIntentSender = delayedFillIntentSender;

@@ -462,7 +472,8 @@ public final class FillRequest implements Parcelable {
                        | FLAG_COMPATIBILITY_MODE_REQUEST
                        | FLAG_PASSWORD_INPUT_TYPE
                        | FLAG_VIEW_NOT_FOCUSED
                        | FLAG_SUPPORTS_FILL_DIALOG);
                        | FLAG_SUPPORTS_FILL_DIALOG
                        | FLAG_IME_SHOWING);
        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
        this.mDelayedFillIntentSender = delayedFillIntentSender;

@@ -484,10 +495,10 @@ public final class FillRequest implements Parcelable {
    };

    @DataClass.Generated(
            time = 1647644111186L,
            time = 1647856966565L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
    @Deprecated
    private void __metadata() {}

+54 −12
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view.autofill;

import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
@@ -76,6 +77,7 @@ import android.view.ContentInfo;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -667,6 +669,9 @@ public final class AutofillManager {

    private final boolean mIsFillDialogEnabled;

    // Indicates whether called the showAutofillDialog() method.
    private boolean mShowAutofillDialogCalled = false;

    /** @hide */
    public interface AutofillClient {
        /**
@@ -1123,6 +1128,14 @@ public final class AutofillManager {
        notifyViewEntered(view, flags);
    }

    private int getImeStateFlag(View v) {
        final WindowInsets rootWindowInsets = v.getRootWindowInsets();
        if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
            return FLAG_IME_SHOWING;
        }
        return 0;
    }

    @GuardedBy("mLock")
    private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
        if (isDisabledByServiceLocked()) {
@@ -1202,6 +1215,8 @@ public final class AutofillManager {
                    flags |= FLAG_PASSWORD_INPUT_TYPE;
                }

                flags |= getImeStateFlag(view);

                if (!isActiveLocked()) {
                    // Starts new session.
                    startSessionLocked(id, null, value, flags);
@@ -1378,6 +1393,8 @@ public final class AutofillManager {
                    flags |= FLAG_PASSWORD_INPUT_TYPE;
                }

                flags |= getImeStateFlag(view);

                if (!isActiveLocked()) {
                    // Starts new session.
                    startSessionLocked(id, bounds, null, flags);
@@ -1493,7 +1510,7 @@ public final class AutofillManager {
                value = view.getAutofillValue();
            }

            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view));
        }
    }

@@ -1518,7 +1535,7 @@ public final class AutofillManager {
            }

            final AutofillId id = getAutofillId(view, virtualId);
            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
            updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, getImeStateFlag(view));
        }
    }

@@ -2110,6 +2127,8 @@ public final class AutofillManager {
        mIdShownFillUi = null;
        mIsFillRequested = false;
        mRequireAutofill = false;
        mShowAutofillDialogCalled = false;
        mFillDialogTriggerIds = null;
        if (resetEnteredIds) {
            mEnteredIds = null;
        }
@@ -3126,8 +3145,9 @@ public final class AutofillManager {
     * dialog-style UI</a> are available for {@code view}, shows a dialog allowing the user to
     * select a suggestion and returns {@code true}.
     * <p>
     * The dialog may not be available if the autofill service does not support it, or if the
     * autofill request has not returned a response yet.
     * The dialog may not be shown if the autofill service does not support it, if the autofill
     * request has not returned a response yet, if the dialog was shown previously, or if the
     * input method is already shown.
     * <p>
     * It is recommended apps to call this method the first time a user focuses on
     * an autofill-able form, and to avoid showing the input method if the dialog is shown. If
@@ -3143,9 +3163,16 @@ public final class AutofillManager {
    // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
    public boolean showAutofillDialog(@NonNull View view) {
        Objects.requireNonNull(view);
        if (shouldShowAutofillDialog(view.getAutofillId())) {
            // If the id matches a trigger id, this will trigger the fill dialog.
            notifyViewEntered(view);
        if (shouldShowAutofillDialog(view, view.getAutofillId())) {
            mShowAutofillDialogCalled = true;
            final WeakReference<View> wrView = new WeakReference<>(view);
            // The id matches a trigger id, this will trigger the fill dialog.
            post(() -> {
                final View v = wrView.get();
                if (v != null) {
                    notifyViewEntered(v);
                }
            });
            return true;
        }
        return false;
@@ -3159,18 +3186,33 @@ public final class AutofillManager {
    // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
    public boolean showAutofillDialog(@NonNull View view, int virtualId) {
        Objects.requireNonNull(view);
        if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) {
            // If the id matches a trigger id, this will trigger the fill dialog.
            notifyViewEntered(view, virtualId, /* bounds= */ null, /* flags= */ 0);
        if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) {
            mShowAutofillDialogCalled = true;
            final WeakReference<View> wrView = new WeakReference<>(view);
            // The id matches a trigger id, this will trigger the fill dialog.
            post(() -> {
                final View v = wrView.get();
                if (v != null) {
                    notifyViewEntered(v, virtualId, /* bounds= */ null, /* flags= */ 0);
                }
            });
            return true;
        }
        return false;
    }

    private boolean shouldShowAutofillDialog(AutofillId id) {
        if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
    private boolean shouldShowAutofillDialog(View view, AutofillId id) {
        if (!hasFillDialogUiFeature()
                || mShowAutofillDialogCalled
                || mFillDialogTriggerIds == null) {
            return false;
        }

        if (getImeStateFlag(view) == FLAG_IME_SHOWING) {
            // IME is showing
            return false;
        }

        final int size = mFillDialogTriggerIds.size();
        for (int i = 0; i < size; i++) {
            AutofillId fillId = mFillDialogTriggerIds.get(i);
+18 −1
Original line number Diff line number Diff line
@@ -11458,7 +11458,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                // Show the IME, except when selecting in read-only text.
                final InputMethodManager imm = getInputMethodManager();
                viewClicked(imm);
                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
                        && !showAutofillDialog()) {
                    imm.showSoftInput(this, 0);
                }
@@ -11476,6 +11477,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return superResult;
    }
    /**
     * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
     * If autofill suggestions are available when the user clicks on a field that supports filling
     * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
     * to display the datasets, so it is easy for users to pay attention to the datasets and
     * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better
     * experience is not to show the IME if there is a fill dialog.
     */
    private boolean showAutofillDialog() {
        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
        if (afm != null) {
            return afm.showAutofillDialog(this);
        }
        return false;
    }
    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
+8 −3
Original line number Diff line number Diff line
@@ -3255,7 +3255,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState

    @Override
    public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
            @Nullable AutofillValue value) {
            @Nullable AutofillValue value, int flags) {
        synchronized (mLock) {
            if (mDestroyed) {
                Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
@@ -3289,7 +3289,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            return;
        }

        if (requestShowFillDialog(response, filledId, filterText)) {
        if (requestShowFillDialog(response, filledId, filterText, flags)) {
            synchronized (mLock) {
                final ViewState currentView = mViewStates.get(mCurrentViewId);
                currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
@@ -3394,12 +3394,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    }

    private boolean requestShowFillDialog(FillResponse response,
            AutofillId filledId, String filterText) {
            AutofillId filledId, String filterText, int flags) {
        if (!isFillDialogUiEnabled()) {
            // Unsupported fill dialog UI
            return false;
        }

        if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
            // IME is showing, fallback to normal suggestions UI
            return false;
        }

        final AutofillId[] ids = response.getFillDialogTriggerIds();
        if (ids == null || !ArrayUtils.contains(ids, filledId)) {
            return false;
+3 −3
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ final class ViewState {
         * Called when the fill UI is ready to be shown for this view.
         */
        void onFillReady(@NonNull FillResponse fillResponse, @NonNull AutofillId focusedId,
                @Nullable AutofillValue value);
                @Nullable AutofillValue value, int flags);
    }

    private static final String TAG = "ViewState";
@@ -202,7 +202,7 @@ final class ViewState {

    /**
     * Calls {@link
     * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
     * Listener#onFillReady(FillResponse, AutofillId, AutofillValue, int)} if the
     * fill UI is ready to be displayed (i.e. when response and bounds are set).
     */
    void maybeCallOnFillReady(int flags) {
@@ -213,7 +213,7 @@ final class ViewState {
        // First try the current response associated with this View.
        if (mResponse != null) {
            if (mResponse.getDatasets() != null || mResponse.getAuthentication() != null) {
                mListener.onFillReady(mResponse, this.id, mCurrentValue);
                mListener.onFillReady(mResponse, this.id, mCurrentValue, flags);
            }
        }
    }