Skip to content

Commit

Permalink
Use WindowInsetsCompat for Keyboard Events
Browse files Browse the repository at this point in the history
Summary:
RN for Android fires `keyboardDidShow` and `keyboardDidHide` by observing changes to viewable window size. This isn't always reliable, such as when an activity has `awindowSoftInputMode` set to not have the system adjust the viewport when a keyboard is opened.

Android 11 added the direct ability to measure and check visibility of the soft keyboard via `WindowInsets`, which fixes these issues. This is exposed downlevel to API 23 via WindowInsetsComapt` with the same limitations as previously, but using it simplifies our calculations a lot.

Changelog:
[Android][Fixed] - Use WindowInsetsCompat for Keyboard Events

Reviewed By: javache

Differential Revision: D38500859

fbshipit-source-id: d4ad41d7e75e4b9c14a485539a5f9de19de74362
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Aug 11, 2022
1 parent a379879 commit 1e48274
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 7 deletions.
9 changes: 5 additions & 4 deletions Libraries/Components/Keyboard/Keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ class Keyboard {
* - `keyboardWillChangeFrame`
* - `keyboardDidChangeFrame`
*
* Note that if you set `android:windowSoftInputMode` to `adjustResize` or `adjustNothing`,
* only `keyboardDidShow` and `keyboardDidHide` events will be available on Android.
* `keyboardWillShow` as well as `keyboardWillHide` are generally not available on Android
* since there is no native corresponding event.
* Android versions prior to API 30 rely on observing layout changes when
* `android:windowSoftInputMode` is set to `adjustResize` or `adjustPan`.
*
* `keyboardWillShow` as well as `keyboardWillHide` are not available on Android since there is
* no native corresponding event.
*
* @param {function} callback function to be called when the event fires.
*/
Expand Down
67 changes: 64 additions & 3 deletions ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +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.graphics.Canvas;
import android.graphics.Point;
Expand All @@ -31,6 +32,9 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.graphics.Insets;
import androidx.core.view.WindowInsetsCompat;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
Expand Down Expand Up @@ -770,7 +774,11 @@ public void runApplication() {

@VisibleForTesting
/* package */ void simulateCheckForKeyboardForTesting() {
getCustomGlobalLayoutListener().checkForKeyboardEvents();
if (Build.VERSION.SDK_INT >= 23) {
getCustomGlobalLayoutListener().checkForKeyboardEvents();
} else {
getCustomGlobalLayoutListener().checkForKeyboardEventsLegacy();
}
}

private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
Expand Down Expand Up @@ -879,7 +887,8 @@ private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLay
private final Rect mVisibleViewArea;
private final int mMinKeyboardHeightDetected;

private int mKeyboardHeight = 0;
private boolean mKeyboardIsVisible = false;
private int mKeyboardHeight = 0; // Only used in checkForKeyboardEventsLegacy path
private int mDeviceRotation = 0;

/* package */ CustomGlobalLayoutListener() {
Expand All @@ -895,13 +904,62 @@ public void onGlobalLayout() {
|| mReactInstanceManager.getCurrentReactContext() == null) {
return;
}
checkForKeyboardEvents();

// WindowInsetsCompat IME measurement is reliable for API level 23+.
// https://developer.android.com/jetpack/androidx/releases/core#1.5.0-alpha02
if (Build.VERSION.SDK_INT >= 23) {
checkForKeyboardEvents();
} else {
checkForKeyboardEventsLegacy();
}

checkForDeviceOrientationChanges();
checkForDeviceDimensionsChanges();
}

@RequiresApi(api = Build.VERSION_CODES.M)
private void checkForKeyboardEvents() {
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
WindowInsets rootInsets = getRootView().getRootWindowInsets();
WindowInsetsCompat compatRootInsets = WindowInsetsCompat.toWindowInsetsCompat(rootInsets);

boolean keyboardIsVisible = compatRootInsets.isVisible(WindowInsetsCompat.Type.ime());
if (keyboardIsVisible != mKeyboardIsVisible) {
mKeyboardIsVisible = keyboardIsVisible;

if (keyboardIsVisible) {
Insets imeInsets = compatRootInsets.getInsets(WindowInsetsCompat.Type.ime());
Insets barInsets = compatRootInsets.getInsets(WindowInsetsCompat.Type.systemBars());
int height = imeInsets.bottom - barInsets.bottom;

int softInputMode = ((Activity) getContext()).getWindow().getAttributes().softInputMode;
int screenY =
softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
? mVisibleViewArea.bottom - height
: mVisibleViewArea.bottom;

sendEvent(
"keyboardDidShow",
createKeyboardEventPayload(
PixelUtil.toDIPFromPixel(screenY),
PixelUtil.toDIPFromPixel(mVisibleViewArea.left),
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
PixelUtil.toDIPFromPixel(height)));
} else {
sendEvent(
"keyboardDidHide",
createKeyboardEventPayload(
PixelUtil.toDIPFromPixel(mLastHeight),
0,
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
0));
}
}
}

private void checkForKeyboardEventsLegacy() {
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);

int notchHeight = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowInsets insets = getRootView().getRootWindowInsets();
Expand All @@ -919,8 +977,10 @@ private void checkForKeyboardEvents() {

boolean isKeyboardShowingOrKeyboardHeightChanged =
mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected;

if (isKeyboardShowingOrKeyboardHeightChanged) {
mKeyboardHeight = heightDiff;
mKeyboardIsVisible = true;
sendEvent(
"keyboardDidShow",
createKeyboardEventPayload(
Expand All @@ -934,6 +994,7 @@ private void checkForKeyboardEvents() {
boolean isKeyboardHidden = mKeyboardHeight != 0 && heightDiff <= mMinKeyboardHeightDetected;
if (isKeyboardHidden) {
mKeyboardHeight = 0;
mKeyboardIsVisible = false;
sendEvent(
"keyboardDidHide",
createKeyboardEventPayload(
Expand Down

0 comments on commit 1e48274

Please sign in to comment.