diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java index f068a88c30c657..3d955e4b4e5fba 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java @@ -12,6 +12,7 @@ import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import com.facebook.react.bridge.JavaScriptModule; @@ -107,6 +108,97 @@ public void testOnSubmitEditing() throws Throwable { fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_NONE); } + public void testRequestFocusDoesNothing() throws Throwable { + String testId = "textInput1"; + + final ReactEditText reactEditText = getViewByTestId(testId); + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.clearFocus(); + } + }); + waitForBridgeAndUIIdle(); + assertFalse(reactEditText.isFocused()); + + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.requestFocus(); + } + }); + waitForBridgeAndUIIdle(); + + // Calling requestFocus() directly should no-op + assertFalse(reactEditText.isFocused()); + } + + public void testRequestFocusFromJS() throws Throwable { + String testId = "textInput1"; + + final ReactEditText reactEditText = getViewByTestId(testId); + + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.clearFocus(); + } + }); + waitForBridgeAndUIIdle(); + assertFalse(reactEditText.isFocused()); + + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.requestFocusFromJS(); + } + }); + waitForBridgeAndUIIdle(); + assertTrue(reactEditText.isFocused()); + } + + public void testAccessibilityFocus() throws Throwable { + String testId = "textInput1"; + + final ReactEditText reactEditText = getViewByTestId(testId); + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.clearFocus(); + } + }); + waitForBridgeAndUIIdle(); + assertFalse(reactEditText.isFocused()); + + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + reactEditText.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, null); + } + }); + waitForBridgeAndUIIdle(); + assertTrue(reactEditText.isFocused()); + + runTestOnUiThread( + new Runnable() { + @Override + public void run() { + reactEditText.performAccessibilityAction( + AccessibilityNodeInfo.ACTION_CLEAR_FOCUS, null); + } + }); + waitForBridgeAndUIIdle(); + assertFalse(reactEditText.isFocused()); + } + private void fireEditorActionAndCheckRecording( final ReactEditText reactEditText, final int actionId) throws Throwable { fireEditorActionAndCheckRecording(reactEditText, actionId, true); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index da50126bed64fc..d388f389674efa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -68,9 +68,6 @@ public class ReactEditText extends AppCompatEditText { // *TextChanged events should be triggered. This is less expensive than removing the text // listeners and adding them back again after the text change is completed. protected boolean mIsSettingTextFromJS; - // This component is controlled, so we want it to get focused only when JS ask it to do so. - // Whenever android requests focus, except for accessibility click, it will be ignored. - private boolean mShouldAllowFocus; private int mDefaultGravityHorizontal; private int mDefaultGravityVertical; @@ -127,7 +124,6 @@ public ReactEditText(Context context) { mNativeEventCount = 0; mMostRecentEventCount = 0; mIsSettingTextFromJS = false; - mShouldAllowFocus = false; mBlurOnSubmit = null; mDisableFullscreen = false; mListeners = null; @@ -152,10 +148,7 @@ public ReactEditText(Context context) { @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { if (action == AccessibilityNodeInfo.ACTION_CLICK) { - mShouldAllowFocus = true; - requestFocus(); - mShouldAllowFocus = false; - return true; + return requestFocusInternal(); } return super.performAccessibilityAction(host, action, args); } @@ -248,18 +241,18 @@ public void clearFocus() { @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { - // Always return true if we are already focused. This is used by android in certain places, - // such as text selection. - if (isFocused()) { - return true; - } - - if (!mShouldAllowFocus) { - return false; - } + // This is a no-op so that when the OS calls requestFocus(), nothing will happen. ReactEditText + // is a controlled component, which means its focus is controlled by JS, with two exceptions: + // autofocus when it's attached to the window, and responding to accessibility events. In both + // of these cases, we call requestFocusInternal() directly. + return isFocused(); + } + private boolean requestFocusInternal() { setFocusableInTouchMode(true); - boolean focused = super.requestFocus(direction, previouslyFocusedRect); + // We must explicitly call this method on the super class; if we call requestFocus() without + // any arguments, it will call into the overridden requestFocus(int, Rect) above, which no-ops. + boolean focused = super.requestFocus(View.FOCUS_DOWN, null); if (getShowSoftInputOnFocus()) { showSoftKeyboard(); } @@ -461,9 +454,7 @@ public void maybeUpdateTypeface() { // VisibleForTesting from {@link TextInputEventsTestCase}. public void requestFocusFromJS() { - mShouldAllowFocus = true; - requestFocus(); - mShouldAllowFocus = false; + requestFocusInternal(); } /* package */ void clearFocusFromJS() { @@ -754,9 +745,7 @@ public void onAttachedToWindow() { } if (mAutoFocus && !mDidAttachToWindow) { - mShouldAllowFocus = true; - requestFocus(); - mShouldAllowFocus = false; + requestFocusInternal(); } mDidAttachToWindow = true;