From 94972039571e1f3b387e0f63227a6ad13740eaf3 Mon Sep 17 00:00:00 2001 From: Filipp Mikheev Date: Sat, 14 Oct 2023 01:10:48 -0700 Subject: [PATCH] Android: fix ClassCastException in ReactRootView.java when software keyboard is shown (#40755) Summary: Fixes https://github.com/facebook/react-native/issues/40754 Hi all! We noticed that our app started to crash after bumping to RN v0.71.13, anyways after a deeper investigation we also found that the crash occurs in the latest version as well. Crash log: ``` E FATAL EXCEPTION: main Process: com.nfl.fantasy.core.android.debug, PID: 6034 java.lang.ClassCastException: android.app.ContextImpl cannot be cast to android.app.Activity at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.getActivity(ReactRootView.java:926) at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.checkForKeyboardEvents(ReactRootView.java:946) at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.onGlobalLayout(ReactRootView.java:912) at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:1061) ``` The code which causes ClassCastException is following [here](https://github.com/facebook/react-native/blob/ea88fbe229e1d276753ee8e118184274fc872138/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java#L864). In this code explicit type conversion to Activity is not safe because it's not guaranteed by the compiler that context will be compatible with Activity type. The appropriate issue [has been filed](https://github.com/facebook/react-native/issues/40754). ## Changelog: [ANDROID] [FIXED] - Fixed crash occurring in certain native views when keyboard events are fired. Pull Request resolved: https://github.com/facebook/react-native/pull/40755 Test Plan: Tested it manually with the [reference application](https://github.com/kot331107/rnCrashReproducer). Repro steps are as follows: - Build and run the app on Android - Tap the button "Open Modal" - You should see the red popup fragment to the bottom of the screen - Tap on the text input to open software keyboard - Expected: it should show the keyboard and no crash happens. Reviewed By: arushikesarwani94 Differential Revision: D50198424 Pulled By: NickGerleman fbshipit-source-id: a5a6d86334856f4ffbe818150da5793380da4702 --- .../com/facebook/react/ReactRootView.java | 26 +++++++++++++------ .../java/com/facebook/react/RootViewTest.kt | 7 +++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 731dcb9a6d9e2f..ebc5cde391670c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -12,9 +12,7 @@ import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; -import android.app.Activity; import android.content.Context; -import android.content.ContextWrapper; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Point; @@ -856,12 +854,18 @@ public void onGlobalLayout() { checkForDeviceDimensionsChanges(); } - private Activity getActivity() { - Context context = getContext(); - while (!(context instanceof Activity) && context instanceof ContextWrapper) { - context = ((ContextWrapper) context).getBaseContext(); + private @Nullable WindowManager.LayoutParams getWindowLayoutParams() { + View view = ReactRootView.this; + if (view.getLayoutParams() instanceof WindowManager.LayoutParams) { + return (WindowManager.LayoutParams) view.getLayoutParams(); } - return (Activity) context; + while (view.getParent() instanceof View) { + view = (View) view.getParent(); + if (view.getLayoutParams() instanceof WindowManager.LayoutParams) { + return (WindowManager.LayoutParams) view.getLayoutParams(); + } + } + return null; } @RequiresApi(api = Build.VERSION_CODES.R) @@ -881,7 +885,13 @@ private void checkForKeyboardEvents() { Insets barInsets = rootInsets.getInsets(WindowInsets.Type.systemBars()); int height = imeInsets.bottom - barInsets.bottom; - int softInputMode = getActivity().getWindow().getAttributes().softInputMode; + int softInputMode; + WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams(); + if (windowLayoutParams != null) { + softInputMode = windowLayoutParams.softInputMode; + } else { + return; + } int screenY = softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING ? mVisibleViewArea.bottom - height diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt index b0e1cea1cdfd0d..63f84ab272c234 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt @@ -13,6 +13,7 @@ import android.graphics.Insets import android.graphics.Rect import android.view.MotionEvent import android.view.WindowInsets +import android.view.WindowManager import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.CatalystInstance import com.facebook.react.bridge.JavaOnlyArray @@ -211,9 +212,11 @@ class RootViewTest { .setVisible(WindowInsets.Type.ime(), true) .build() } + val rootViewSpy = spy(rootView) + whenever(rootViewSpy.getLayoutParams()).thenReturn(WindowManager.LayoutParams()) - rootView.startReactApplication(instanceManager, "") - rootView.simulateCheckForKeyboardForTesting() + rootViewSpy.startReactApplication(instanceManager, "") + rootViewSpy.simulateCheckForKeyboardForTesting() val params = Arguments.createMap() val endCoordinates = Arguments.createMap()