Allow focusing TextInput when already focused

Summary:
Right now, `requestFocus()` is a no-op if the EditText view thinks it's already focused. In certain cases, though, we still want to focus the view even if it's already focused - for example, if TalkBack is enabled and you dismiss the keyboard, you want to be able to tap on the TextInput again to bring back the keyboard, even though the View never thinks it lost focus.

What I'm doing instead is basically disregarding the View's current focus state if we *would* focus the TextInput, which is in 3 circumstances:

- When the view is attached to a window, if autofocus is true
- When the focus is being requested by JS
- When the focus is being requested by an accessibility action from the OS

Changelog: [Android][Fixed] Change how TextInput responds to requestFocus to fix a11y focus issue

Reviewed By: mdvacca

Differential Revision: D19750312

fbshipit-source-id: 30b9fab40af4a083fa98f57aba7e586540238bea
This commit is contained in:
Emily Janzer
2020-02-18 12:12:16 -08:00
committed by Facebook Github Bot
parent d8ff5a515b
commit d4a498aba2
2 changed files with 105 additions and 24 deletions
@@ -12,6 +12,7 @@ import android.text.style.ForegroundColorSpan;
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 class TextInputTestCase extends ReactAppInstrumentationTestCase {
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);