Merge "Make TextView do not show IME when fill dialog popup" into tm-dev

This commit is contained in:
TYM Tsai 2022-03-31 14:09:17 +00:00 committed by Android (Google) Code Review
commit e6ae224102
5 changed files with 99 additions and 24 deletions

View File

@ -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() {}

View File

@ -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;
@ -70,6 +71,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;
@ -647,6 +649,9 @@ public final class AutofillManager {
private final boolean mIsFillDialogEnabled;
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
/** @hide */
public interface AutofillClient {
/**
@ -1103,6 +1108,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()) {
@ -1182,6 +1195,8 @@ public final class AutofillManager {
flags |= FLAG_PASSWORD_INPUT_TYPE;
}
flags |= getImeStateFlag(view);
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, null, value, flags);
@ -1358,6 +1373,8 @@ public final class AutofillManager {
flags |= FLAG_PASSWORD_INPUT_TYPE;
}
flags |= getImeStateFlag(view);
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, bounds, null, flags);
@ -1473,7 +1490,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));
}
}
@ -1498,7 +1515,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));
}
}
@ -2057,6 +2074,8 @@ public final class AutofillManager {
mIdShownFillUi = null;
mIsFillRequested = false;
mRequireAutofill = false;
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
if (resetEnteredIds) {
mEnteredIds = null;
}
@ -3051,8 +3070,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
@ -3068,9 +3088,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;
@ -3099,18 +3126,33 @@ public final class AutofillManager {
*/
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);

View File

@ -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) {

View File

@ -3182,7 +3182,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: "
@ -3207,7 +3207,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);
@ -3312,12 +3312,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;

View File

@ -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);
}
}
}