From de9005c38f29162916a0b8a7f27318f2aa3106b9 Mon Sep 17 00:00:00 2001 From: TYM Tsai Date: Thu, 14 Apr 2022 17:00:48 +0800 Subject: [PATCH] Fix Autofill dialog does not work Apps have only one chance to get Autofill dialog support, it requires attaching the autofillable view before the Activity has finished laying out. This change moves the doing request to the view being laid out, that is the same timing with what Autofill is doing about the view is auto focused but under different conditions. Bug: 226674898 Test: atest android.autofillservice.cts.dialog.LoginActivityTest Change-Id: I9018b8db8673e3b917a303476666ae766fbe7891 Merged-In: I9018b8db8673e3b917a303476666ae766fbe7891 --- core/java/android/view/View.java | 21 ++++--- core/java/android/view/ViewRootImpl.java | 14 ----- .../view/autofill/AutofillManager.java | 61 ++++++------------- 3 files changed, 31 insertions(+), 65 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index dc3620bcc5e56..19a28ffd4e148 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8206,24 +8206,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (canNotifyAutofillEnterExitEvent()) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (enter && isFocused()) { + if (enter) { // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. if (!isLaidOut()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { - // TODO This is a potential problem that View gets focus before it's visible - // to User. Ideally View should handle the event when isVisibleToUser() - // becomes true where it should issue notifyViewEntered(). - afm.notifyViewEntered(this); - } else { - afm.enableFillRequestActivityStarted(this); + if (isFocused()) { + // TODO This is a potential problem that View gets focus before it's + // visible to User. Ideally View should handle the event when + // isVisibleToUser() becomes true where it should issue + // notifyViewEntered(). + afm.notifyViewEntered(this); + } else { + afm.notifyViewEnteredForFillDialog(this); + } } - } else if (!enter && !isFocused()) { + } else if (!isFocused()) { afm.notifyViewExited(this); - } else if (enter) { - afm.enableFillRequestActivityStarted(this); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fbb86ff3a55a8..4f00bed27cd3e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -585,7 +585,6 @@ public final class ViewRootImpl implements ViewParent, @Nullable int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED; boolean mPerformContentCapture; - boolean mPerformAutoFill; boolean mReportNextDraw; @@ -883,7 +882,6 @@ public final class ViewRootImpl implements ViewParent, mPreviousTransparentRegion = new Region(); mFirst = true; // true for the first time the view is added mPerformContentCapture = true; // also true for the first time the view is added - mPerformAutoFill = true; mAdded = false; mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); @@ -4310,18 +4308,6 @@ public final class ViewRootImpl implements ViewParent, if (mPerformContentCapture) { performContentCaptureInitialReport(); } - - if (mPerformAutoFill) { - notifyEnterForAutoFillIfNeeded(); - } - } - - private void notifyEnterForAutoFillIfNeeded() { - mPerformAutoFill = false; - final AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.notifyViewEnteredForActivityStarted(mView); - } } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 0a75992811f4b..dcedb3083f769 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -102,6 +102,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Cleaner; @@ -644,16 +645,6 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean mEnabledForAugmentedAutofillOnly; - /** - * Indicates whether there are any fields that need to do a fill request - * after the activity starts. - * - * Note: This field will be set to true multiple times if there are many - * autofillable views. So needs to check mIsFillRequested at the same time to - * avoid re-trigger autofill. - */ - private boolean mRequireAutofill; - /** * Indicates whether there is already a field to do a fill request after * the activity started. @@ -663,7 +654,7 @@ public final class AutofillManager { * triggered autofill, it is unnecessary to trigger again through * AutofillManager#notifyViewEnteredForActivityStarted. */ - private boolean mIsFillRequested; + private AtomicBoolean mIsFillRequested; @Nullable private List mFillDialogTriggerIds; @@ -811,8 +802,7 @@ public final class AutofillManager { mContext = Objects.requireNonNull(context, "context cannot be null"); mService = service; mOptions = context.getAutofillOptions(); - mIsFillRequested = false; - mRequireAutofill = false; + mIsFillRequested = new AtomicBoolean(false); mIsFillDialogEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_AUTOFILL, @@ -1113,22 +1103,31 @@ public final class AutofillManager { } /** - * The view have the allowed autofill hints, marked to perform a fill request after layout if - * the field does not trigger a fill request. + * The {@link #DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or the view have + * the allowed autofill hints, performs a fill request to know there is any field supported + * fill dialog. * * @hide */ - public void enableFillRequestActivityStarted(View v) { - if (mRequireAutofill) { + public void notifyViewEnteredForFillDialog(View v) { + // Skip if the fill request has been performed for a view. + if (mIsFillRequested.get()) { return; } if (mIsFillDialogEnabled || ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) { if (sDebug) { - Log.d(TAG, "Trigger fill request at starting"); + Log.d(TAG, "Trigger fill request at view entered"); } - mRequireAutofill = true; + + // Note: No need for atomic getAndSet as this method is called on the UI thread. + mIsFillRequested.set(true); + + int flags = FLAG_SUPPORTS_FILL_DIALOG; + flags |= FLAG_VIEW_NOT_FOCUSED; + // use root view, so autofill UI does not trigger immediately. + notifyViewEntered(v.getRootView(), flags); } } @@ -1136,25 +1135,6 @@ public final class AutofillManager { return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints); } - /** - * Notify autofill to do a fill request while the activity started. - * - * @hide - */ - public void notifyViewEnteredForActivityStarted(@NonNull View view) { - if (!hasAutofillFeature() || !hasFillDialogUiFeature()) { - return; - } - - if (!mRequireAutofill || mIsFillRequested) { - return; - } - - int flags = FLAG_SUPPORTS_FILL_DIALOG; - flags |= FLAG_VIEW_NOT_FOCUSED; - notifyViewEntered(view, flags); - } - private int getImeStateFlag(View v) { final WindowInsets rootWindowInsets = v.getRootWindowInsets(); if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) { @@ -1203,7 +1183,7 @@ public final class AutofillManager { } AutofillCallback callback; synchronized (mLock) { - mIsFillRequested = true; + mIsFillRequested.set(true); callback = notifyViewEnteredLocked(view, flags); } @@ -2119,8 +2099,7 @@ public final class AutofillManager { mFillableIds = null; mSaveTriggerId = null; mIdShownFillUi = null; - mIsFillRequested = false; - mRequireAutofill = false; + mIsFillRequested.set(false); mShowAutofillDialogCalled = false; mFillDialogTriggerIds = null; if (resetEnteredIds) {