From 8bef3b1f1136ab5c2f2309a3101a7d9626ced1f5 Mon Sep 17 00:00:00 2001 From: fabriziobertoglio1987 Date: Tue, 28 Sep 2021 10:41:10 -0700 Subject: [PATCH] compute correct Keyboard Height with Notch (#30919) Summary: fixes https://github.com/facebook/react-native/issues/27089 fixes https://github.com/facebook/react-native/issues/30191 fixes https://github.com/facebook/react-native/issues/26296 fixes https://github.com/facebook/react-native/issues/24353 Related https://github.com/facebook/react-native/issues/30052 https://github.com/facebook/react-native/issues/28004 https://github.com/facebook/react-native/issues/26536 The keyboard height of event keyboardDidShow is computed as the difference of two variables: - The screen height excluding the Android Notch DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels returns the screen height excluding the Android Notch - The Visible Area excluding the Keyboard, but including the Android Notch getWindowVisibleDisplayFrame() which returns the visible area including the Android Notch The computation of the keyboard height is wrong when the device has an Android Notch. This pr adds the Android Notch computation for API levels 28+ More info at https://github.com/facebook/react-native/issues/27089#issuecomment-775821333 ## Changelog [Android] [Fixed] - Compute Android Notch in keyboardDidShow height calculation API 28+ Pull Request resolved: https://github.com/facebook/react-native/pull/30919 Test Plan: adding a ReactRootViewTest for keyboardDidShow verifying correct functionality on API < 28 **
TEST CASE - BEFORE FIX**

**WITHOUT NOTCH** - The black view on the bottom is visible - The keyboard height is 282 | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | | | **WITH NOTCH** - The black view on the bottom is **not** visible. The black view is not visible because keyboardDidHide is sending the wrong keyboard height value. - The keyboard height changes to 234. The keyboard height is the same from the previous test, but the value sent from keyboardDidHide changed for the Notch. | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | | |

**
TEST CASE - AFTER FIX**

**WITH NOTCH** - The black view on the bottom is visible - The keyboard height is 282 | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | | |

Reviewed By: ShikaSD Differential Revision: D31207989 Pulled By: cortinico fbshipit-source-id: 0955a3884201122166c5c0ae2aca988a0ed4af53 --- .../com/facebook/react/ReactRootView.java | 18 +++++++- .../java/com/facebook/react/RootViewTest.java | 43 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 13074f8963e..5bfdf9aafd8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -16,8 +16,10 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.util.AttributeSet; +import android.view.DisplayCutout; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -647,6 +649,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { mJSTouchDispatcher = new JSTouchDispatcher(this); } + @VisibleForTesting + /* package */ void simulateCheckForKeyboardForTesting() { + getCustomGlobalLayoutListener().checkForKeyboardEvents(); + } + private CustomGlobalLayoutListener getCustomGlobalLayoutListener() { if (mCustomGlobalLayoutListener == null) { mCustomGlobalLayoutListener = new CustomGlobalLayoutListener(); @@ -766,8 +773,17 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { private void checkForKeyboardEvents() { getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea); + int notchHeight = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DisplayCutout displayCutout = getRootView().getRootWindowInsets().getDisplayCutout(); + if (displayCutout != null) { + notchHeight = displayCutout.getSafeInsetTop(); + } + } final int heightDiff = - DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom; + DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels + - mVisibleViewArea.bottom + + notchHeight; boolean isKeyboardShowingOrKeyboardHeightChanged = mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected; diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java index c0a28af8f86..6389c29d717 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.graphics.Rect; import android.view.MotionEvent; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.CatalystInstance; @@ -25,7 +26,9 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.SystemClock; +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.Event; @@ -37,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; @@ -209,4 +213,43 @@ public class RootViewTest { rootView.unmountReactApplication(); rootView.startReactApplication(instanceManager, ""); } + + @Test + public void testCheckForKeyboardEvents() { + ReactInstanceManager instanceManager = mock(ReactInstanceManager.class); + RCTDeviceEventEmitter eventEmitterModuleMock = mock(RCTDeviceEventEmitter.class); + + when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext); + when(mReactContext.getJSModule(RCTDeviceEventEmitter.class)).thenReturn(eventEmitterModuleMock); + + ReactRootView rootView = + new ReactRootView(mReactContext) { + @Override + public void getWindowVisibleDisplayFrame(Rect outRect) { + if (outRect.bottom == 0) { + outRect.bottom += 100; + outRect.right += 370; + } else { + outRect.bottom += 370; + } + } + }; + + rootView.startReactApplication(instanceManager, ""); + rootView.simulateCheckForKeyboardForTesting(); + + WritableMap params = Arguments.createMap(); + WritableMap endCoordinates = Arguments.createMap(); + double screenHeight = 470.0; + double keyboardHeight = 100.0; + params.putDouble("duration", 0.0); + endCoordinates.putDouble("width", screenHeight - keyboardHeight); + endCoordinates.putDouble("screenX", 0.0); + endCoordinates.putDouble("height", screenHeight - keyboardHeight); + endCoordinates.putDouble("screenY", keyboardHeight); + params.putMap("endCoordinates", endCoordinates); + params.putString("easing", "keyboard"); + + verify(eventEmitterModuleMock, Mockito.times(1)).emit("keyboardDidShow", params); + } }