Skip to content

Commit 0ebaaed

Browse files
authored
Fix broken focus behavior for TextInput in older Android versions (< 9) (#51964)
1 parent e386b01 commit 0ebaaed

22 files changed

+47
-180
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<b263bdcbc1258f7d5c56e69732ba9076>>
7+
* @generated SignedSource<<fa641112b3a8888ba2b24cf8829e9382>>
88
*/
99

1010
/**
@@ -250,12 +250,6 @@ public object ReactNativeFeatureFlags {
250250
@JvmStatic
251251
public fun useAlwaysAvailableJSErrorHandling(): Boolean = accessor.useAlwaysAvailableJSErrorHandling()
252252

253-
/**
254-
* If true, focusing in ReactEditText will mainly use stock Android requestFocus() behavior. If false it will use legacy custom focus behavior.
255-
*/
256-
@JvmStatic
257-
public fun useEditTextStockAndroidFocusBehavior(): Boolean = accessor.useEditTextStockAndroidFocusBehavior()
258-
259253
/**
260254
* Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly.
261255
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<0496ecf3d1e5d8a2e6d4d594aca806d0>>
7+
* @generated SignedSource<<9ecb711480b7d6c22bac380c28d035bc>>
88
*/
99

1010
/**
@@ -57,7 +57,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
5757
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
5858
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
5959
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
60-
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
6160
private var useFabricInteropCache: Boolean? = null
6261
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
6362
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
@@ -399,15 +398,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
399398
return cached
400399
}
401400

402-
override fun useEditTextStockAndroidFocusBehavior(): Boolean {
403-
var cached = useEditTextStockAndroidFocusBehaviorCache
404-
if (cached == null) {
405-
cached = ReactNativeFeatureFlagsCxxInterop.useEditTextStockAndroidFocusBehavior()
406-
useEditTextStockAndroidFocusBehaviorCache = cached
407-
}
408-
return cached
409-
}
410-
411401
override fun useFabricInterop(): Boolean {
412402
var cached = useFabricInteropCache
413403
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<c4f3b0cee8b9b4b9cebb589801e1dd15>>
7+
* @generated SignedSource<<2151e5ec5d04924e742f37b527dc23b9>>
88
*/
99

1010
/**
@@ -102,8 +102,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
102102

103103
@DoNotStrip @JvmStatic public external fun useAlwaysAvailableJSErrorHandling(): Boolean
104104

105-
@DoNotStrip @JvmStatic public external fun useEditTextStockAndroidFocusBehavior(): Boolean
106-
107105
@DoNotStrip @JvmStatic public external fun useFabricInterop(): Boolean
108106

109107
@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<a8900217ae0385947b619c8fa0834942>>
7+
* @generated SignedSource<<56f86a3a0c0bbf453cf45a0db541ef54>>
88
*/
99

1010
/**
@@ -97,8 +97,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
9797

9898
override fun useAlwaysAvailableJSErrorHandling(): Boolean = false
9999

100-
override fun useEditTextStockAndroidFocusBehavior(): Boolean = true
101-
102100
override fun useFabricInterop(): Boolean = false
103101

104102
override fun useNativeViewConfigsInBridgelessMode(): Boolean = false

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<8f5180a0ef154c083ac38d28e650ee11>>
7+
* @generated SignedSource<<5b016fd6298477856116736e37c37c6f>>
88
*/
99

1010
/**
@@ -61,7 +61,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
6161
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
6262
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
6363
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
64-
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
6564
private var useFabricInteropCache: Boolean? = null
6665
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
6766
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
@@ -440,16 +439,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
440439
return cached
441440
}
442441

443-
override fun useEditTextStockAndroidFocusBehavior(): Boolean {
444-
var cached = useEditTextStockAndroidFocusBehaviorCache
445-
if (cached == null) {
446-
cached = currentProvider.useEditTextStockAndroidFocusBehavior()
447-
accessedFeatureFlags.add("useEditTextStockAndroidFocusBehavior")
448-
useEditTextStockAndroidFocusBehaviorCache = cached
449-
}
450-
return cached
451-
}
452-
453442
override fun useFabricInterop(): Boolean {
454443
var cached = useFabricInteropCache
455444
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<33571f99b1f78fbc62cecfca5f8351fa>>
7+
* @generated SignedSource<<b4d6157922f6182dd588d5ae5b54ead9>>
88
*/
99

1010
/**
@@ -97,8 +97,6 @@ public interface ReactNativeFeatureFlagsProvider {
9797

9898
@DoNotStrip public fun useAlwaysAvailableJSErrorHandling(): Boolean
9999

100-
@DoNotStrip public fun useEditTextStockAndroidFocusBehavior(): Boolean
101-
102100
@DoNotStrip public fun useFabricInterop(): Boolean
103101

104102
@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import android.view.MenuItem;
3737
import android.view.MotionEvent;
3838
import android.view.View;
39+
import android.view.ViewGroup;
3940
import android.view.accessibility.AccessibilityNodeInfo;
4041
import android.view.inputmethod.EditorInfo;
4142
import android.view.inputmethod.InputConnection;
@@ -146,9 +147,6 @@ public class ReactEditText extends AppCompatEditText {
146147

147148
public ReactEditText(Context context) {
148149
super(context);
149-
if (!ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) {
150-
setFocusableInTouchMode(false);
151-
}
152150

153151
mInputMethodManager =
154152
(InputMethodManager)
@@ -191,9 +189,7 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) {
191189
// selection on accessibility click to undo that.
192190
setSelection(length);
193191
}
194-
return ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()
195-
? requestFocusProgramatically()
196-
: requestFocusInternal();
192+
return requestFocusProgramatically();
197193
}
198194
return super.performAccessibilityAction(host, action, args);
199195
}
@@ -341,29 +337,28 @@ public boolean onTextContextMenuItem(int id) {
341337
return super.onTextContextMenuItem(id);
342338
}
343339

344-
@Override
345-
public void clearFocus() {
346-
boolean useStockFocusBehavior = ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior();
347-
if (!useStockFocusBehavior) {
348-
setFocusableInTouchMode(false);
340+
public void clearFocusAndMaybeRefocus() {
341+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P || !isInTouchMode()) {
342+
super.clearFocus();
343+
} else {
344+
// Avoid refocusing to a new view on old versions of Android by default
345+
// by preventing `requestFocus()` on the rootView from moving focus to any child.
346+
// https://cs.android.com/android/_/android/platform/frameworks/base/+/bdc66cb5a0ef513f4306edf9156cc978b08e06e4
347+
ViewGroup rootViewGroup = (ViewGroup)getRootView();
348+
int oldDescendantFocusability = rootViewGroup.getDescendantFocusability();
349+
rootViewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
350+
super.clearFocus();
351+
rootViewGroup.setDescendantFocusability(oldDescendantFocusability);
349352
}
350-
super.clearFocus();
353+
351354
hideSoftKeyboard();
352355
}
353356

354-
@Override
355-
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
356-
// This is a no-op so that when the OS calls requestFocus(), nothing will happen. ReactEditText
357-
// is a controlled component, which means its focus is controlled by JS, with two exceptions:
358-
// autofocus when it's attached to the window, and responding to accessibility events. In both
359-
// of these cases, we call requestFocusInternal() directly.
360-
return ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()
361-
? super.requestFocus(direction, previouslyFocusedRect)
362-
: isFocused();
357+
/* package */ void clearFocusFromJS() {
358+
clearFocusAndMaybeRefocus();
363359
}
364360

365361
private boolean requestFocusInternal() {
366-
setFocusableInTouchMode(true);
367362
// We must explicitly call this method on the super class; if we call requestFocus() without
368363
// any arguments, it will call into the overridden requestFocus(int, Rect) above, which no-ops.
369364
boolean focused = super.requestFocus(View.FOCUS_DOWN, null);
@@ -656,15 +651,7 @@ public void maybeUpdateTypeface() {
656651

657652
// VisibleForTesting from {@link TextInputEventsTestCase}.
658653
public void requestFocusFromJS() {
659-
if (ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) {
660-
requestFocusProgramatically();
661-
} else {
662-
requestFocusInternal();
663-
}
664-
}
665-
666-
/* package */ void clearFocusFromJS() {
667-
clearFocus();
654+
requestFocusProgramatically();
668655
}
669656

670657
// VisibleForTesting from {@link TextInputEventsTestCase}.
@@ -1107,11 +1094,7 @@ public void onAttachedToWindow() {
11071094
}
11081095

11091096
if (mAutoFocus && !mDidAttachToWindow) {
1110-
if (ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) {
1111-
requestFocusProgramatically();
1112-
} else {
1113-
requestFocusInternal();
1114-
}
1097+
requestFocusProgramatically();
11151098
}
11161099

11171100
mDidAttachToWindow = true;

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,7 @@ protected void addEventEmitters(
11641164
}
11651165

11661166
if (shouldBlur) {
1167-
editText.clearFocus();
1167+
editText.clearFocusAndMaybeRefocus();
11681168
}
11691169

11701170
// Prevent default behavior except when we want it to insert a newline.

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<83b23039ed9fff5109ff3b532648baac>>
7+
* @generated SignedSource<<cf0734c38bab916ecaf361bc557b8802>>
88
*/
99

1010
/**
@@ -261,12 +261,6 @@ class ReactNativeFeatureFlagsProviderHolder
261261
return method(javaProvider_);
262262
}
263263

264-
bool useEditTextStockAndroidFocusBehavior() override {
265-
static const auto method =
266-
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useEditTextStockAndroidFocusBehavior");
267-
return method(javaProvider_);
268-
}
269-
270264
bool useFabricInterop() override {
271265
static const auto method =
272266
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useFabricInterop");
@@ -498,11 +492,6 @@ bool JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling(
498492
return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling();
499493
}
500494

501-
bool JReactNativeFeatureFlagsCxxInterop::useEditTextStockAndroidFocusBehavior(
502-
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
503-
return ReactNativeFeatureFlags::useEditTextStockAndroidFocusBehavior();
504-
}
505-
506495
bool JReactNativeFeatureFlagsCxxInterop::useFabricInterop(
507496
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
508497
return ReactNativeFeatureFlags::useFabricInterop();
@@ -680,9 +669,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
680669
makeNativeMethod(
681670
"useAlwaysAvailableJSErrorHandling",
682671
JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling),
683-
makeNativeMethod(
684-
"useEditTextStockAndroidFocusBehavior",
685-
JReactNativeFeatureFlagsCxxInterop::useEditTextStockAndroidFocusBehavior),
686672
makeNativeMethod(
687673
"useFabricInterop",
688674
JReactNativeFeatureFlagsCxxInterop::useFabricInterop),

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<efb0288fd19fb35e4582522c835301b4>>
7+
* @generated SignedSource<<f295d109e9a81ce2d2040a5fc89e9ada>>
88
*/
99

1010
/**
@@ -141,9 +141,6 @@ class JReactNativeFeatureFlagsCxxInterop
141141
static bool useAlwaysAvailableJSErrorHandling(
142142
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
143143

144-
static bool useEditTextStockAndroidFocusBehavior(
145-
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
146-
147144
static bool useFabricInterop(
148145
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
149146

0 commit comments

Comments
 (0)