Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ abstract class GenerateEntryPointTask : DefaultTask() {
DefaultNewArchitectureEntryPoint.load();
}

if ({{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
}
WindowUtilKt.initEdgeToEdge(context, {{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED);
}
}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ class GenerateEntryPointTaskTest {
DefaultNewArchitectureEntryPoint.load();
}

if (com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
}
WindowUtilKt.initEdgeToEdge(context, com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED);
}
}
"""
Expand Down
6 changes: 2 additions & 4 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -3275,13 +3275,10 @@ public abstract class com/facebook/react/uimanager/BaseViewManagerDelegate : com

public final class com/facebook/react/uimanager/DisplayMetricsHolder {
public static final field INSTANCE Lcom/facebook/react/uimanager/DisplayMetricsHolder;
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun initDisplayMetrics (Landroid/content/Context;)V
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
}

public final class com/facebook/react/uimanager/FloatUtil {
Expand Down Expand Up @@ -6540,7 +6537,8 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion {

public final class com/facebook/react/views/view/WindowUtilKt {
public static final fun isEdgeToEdgeFeatureFlagOn ()Z
public static final fun setEdgeToEdgeFeatureFlagOn ()V
public static final fun isEdgeToEdge ()Z
public static final fun initEdgeToEdge (Landroid/content/Context;Z)V
}

public final class com/facebook/react/views/virtual/VirtualViewMode : java/lang/Enum {
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)

api(libs.fbjni)
api(libs.fresco)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.BlendMode;
import android.graphics.Canvas;
Expand All @@ -37,6 +38,9 @@
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.window.layout.WindowMetricsCalculator;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
Expand Down Expand Up @@ -72,6 +76,7 @@
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.view.WindowUtilKt;
import com.facebook.systrace.Systrace;
import java.util.concurrent.atomic.AtomicInteger;

Expand Down Expand Up @@ -1020,10 +1025,28 @@ private void checkForKeyboardEventsLegacy() {
}
}
}
final int heightDiff =
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels
- mVisibleViewArea.bottom
+ notchHeight;

int heightPixels = getContext().getResources().getDisplayMetrics().heightPixels;
final ReactContext reactContext = getCurrentReactContext();
Copy link
Contributor Author

@zoontek zoontek Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same logic used in DeviceInfoModule.kt getWindowDisplayMetrics, but it has been inlined instead of extracted as a function, since checkForKeyboardEventsLegacy will be deleted here

final Activity activity = reactContext != null ? reactContext.getCurrentActivity() : null;

if (activity != null) {
heightPixels = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity).getBounds().height();

if (!WindowUtilKt.isEdgeToEdge()) {
WindowInsetsCompat rootWindowInsets =
ViewCompat.getRootWindowInsets(activity.getWindow().getDecorView());

if (rootWindowInsets != null) {
androidx.core.graphics.Insets insets =
rootWindowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
heightPixels -= (insets.top + insets.bottom);
}
}
}

final int heightDiff = heightPixels - mVisibleViewArea.bottom + notchHeight;

boolean isKeyboardShowingOrKeyboardHeightChanged =
mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@

package com.facebook.react.modules.deviceinfo

import android.util.DisplayMetrics
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.window.layout.WindowMetricsCalculator
import com.facebook.fbreact.specs.NativeDeviceInfoSpec
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactNoCrashSoftException
import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
import com.facebook.react.uimanager.DisplayMetricsHolder.getScreenDisplayMetrics
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
import com.facebook.react.views.view.isEdgeToEdge

/** Module that exposes Android Constants to JS. */
@ReactModule(name = NativeDeviceInfoSpec.NAME)
Expand All @@ -30,15 +36,62 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
reactContext.addLifecycleEventListener(this)
}

private fun getWindowDisplayMetrics(): DisplayMetrics {
val windowDisplayMetrics = DisplayMetrics()
windowDisplayMetrics.setTo(reactApplicationContext.resources.displayMetrics)

val activity = reactApplicationContext.currentActivity ?: return windowDisplayMetrics
val bounds = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).bounds
var width = bounds.width()
var height = bounds.height()

// WindowMetrics bounds include system bars. When edge-to-edge is not enabled, we subtract them
// so that window dimensions reflect the usable content area.
if (!isEdgeToEdge) {
ViewCompat.getRootWindowInsets(activity.window.decorView)?.let {
val insets = it.getInsets(WindowInsetsCompat.Type.systemBars())
width -= (insets.left + insets.right)
height -= (insets.top + insets.bottom)
}
}

windowDisplayMetrics.widthPixels = width
windowDisplayMetrics.heightPixels = height
return windowDisplayMetrics
}

fun getDisplayMetricsWritableMap(): WritableMap =
WritableNativeMap().apply {
putMap(
"windowPhysicalPixels",
getPhysicalPixelsWritableMap(getWindowDisplayMetrics()),
)
putMap(
"screenPhysicalPixels",
getPhysicalPixelsWritableMap(getScreenDisplayMetrics()),
)
}

private fun getPhysicalPixelsWritableMap(
displayMetrics: DisplayMetrics,
): WritableMap =
WritableNativeMap().apply {
putInt("width", displayMetrics.widthPixels)
putInt("height", displayMetrics.heightPixels)
putDouble("scale", displayMetrics.density.toDouble())
putDouble("fontScale", fontScale.toDouble())
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
}

public override fun getTypedExportedConstants(): Map<String, Any> {
val displayMetrics = getDisplayMetricsWritableMap(fontScale.toDouble())
val displayMetrics = getDisplayMetricsWritableMap()

// Cache the initial dimensions for later comparison in emitUpdateDimensionsEvent
previousDisplayMetrics = displayMetrics.copy()

return mapOf(
"Dimensions" to displayMetrics.toHashMap(),
"isEdgeToEdge" to isEdgeToEdgeFeatureFlagOn,
"isEdgeToEdge" to isEdgeToEdge,
)
}

Expand All @@ -58,7 +111,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
reactApplicationContext.let { context ->
if (context.hasActiveReactInstance()) {
// Don't emit an event to JS if the dimensions haven't changed
val displayMetrics = getDisplayMetricsWritableMap(fontScale.toDouble())
val displayMetrics = getDisplayMetricsWritableMap()
if (previousDisplayMetrics == null) {
previousDisplayMetrics = displayMetrics.copy()
} else if (displayMetrics != previousDisplayMetrics) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.facebook.react.common.ReactConstants
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
import com.facebook.react.views.view.isEdgeToEdge
import com.facebook.react.views.view.setStatusBarTranslucency
import com.facebook.react.views.view.setStatusBarVisibility

Expand Down Expand Up @@ -56,7 +56,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
)
return
}
if (isEdgeToEdgeFeatureFlagOn) {
if (isEdgeToEdge) {
FLog.w(
ReactConstants.TAG,
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.",
Expand Down Expand Up @@ -93,7 +93,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
)
return
}
if (isEdgeToEdgeFeatureFlagOn) {
if (isEdgeToEdge) {
FLog.w(
ReactConstants.TAG,
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.uimanager.PixelUtil.pxToDp

/**
Expand All @@ -26,21 +24,8 @@ public object DisplayMetricsHolder {
private const val INITIALIZATION_MISSING_MESSAGE =
"DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"

@JvmStatic private var windowDisplayMetrics: DisplayMetrics? = null
@JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null

/** The metrics of the window associated to the Context used to initialize ReactNative */
@JvmStatic
public fun getWindowDisplayMetrics(): DisplayMetrics {
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
return windowDisplayMetrics as DisplayMetrics
}

@JvmStatic
public fun setWindowDisplayMetrics(displayMetrics: DisplayMetrics?) {
windowDisplayMetrics = displayMetrics
}

/** Screen metrics returns the metrics of the default screen on the device. */
@JvmStatic
public fun getScreenDisplayMetrics(): DisplayMetrics {
Expand All @@ -62,11 +47,10 @@ public object DisplayMetricsHolder {
}

@JvmStatic
@SuppressLint("DeprecatedMethod") // for Andriod Lint
@SuppressLint("DeprecatedMethod") // for Android Lint
@Suppress("DEPRECATION") // for Kotlin compiler
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
windowDisplayMetrics = displayMetrics
val screenDisplayMetrics = DisplayMetrics()
screenDisplayMetrics.setTo(displayMetrics)
try {
Expand All @@ -84,35 +68,6 @@ public object DisplayMetricsHolder {
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
}

@JvmStatic
public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods have been moved to DeviceInfoModule.kt, as they are only used there.

checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }

return WritableNativeMap().apply {
putMap(
"windowPhysicalPixels",
getPhysicalPixelsWritableMap(windowDisplayMetrics as DisplayMetrics, fontScale),
)
putMap(
"screenPhysicalPixels",
getPhysicalPixelsWritableMap(screenDisplayMetrics as DisplayMetrics, fontScale),
)
}
}

private fun getPhysicalPixelsWritableMap(
displayMetrics: DisplayMetrics,
fontScale: Double,
): WritableMap =
WritableNativeMap().apply {
putInt("width", displayMetrics.widthPixels)
putInt("height", displayMetrics.heightPixels)
putDouble("scale", displayMetrics.density.toDouble())
putDouble("fontScale", fontScale)
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
}

internal fun getStatusBarHeightPx(activity: Activity?): Int {
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
return windowInsets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import com.facebook.react.views.common.ContextUtils
import com.facebook.react.views.view.ReactViewGroup
import com.facebook.react.views.view.disableEdgeToEdge
import com.facebook.react.views.view.enableEdgeToEdge
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
import com.facebook.react.views.view.isEdgeToEdge
import com.facebook.react.views.view.setStatusBarTranslucency

/**
Expand Down Expand Up @@ -81,17 +81,17 @@ public class ReactModalHostView(context: ThemedReactContext) :
public var onRequestCloseListener: OnRequestCloseListener? = null

public var statusBarTranslucent: Boolean = false
get() = field || isEdgeToEdgeFeatureFlagOn
get() = field || isEdgeToEdge
set(value) {
field = value
createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn
createNewDialog = createNewDialog || !isEdgeToEdge
}

public var navigationBarTranslucent: Boolean = false
get() = field || isEdgeToEdgeFeatureFlagOn
get() = field || isEdgeToEdge
set(value) {
field = value
createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn
createNewDialog = createNewDialog || !isEdgeToEdge
}

public var animationType: String? = null
Expand Down Expand Up @@ -428,7 +428,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
val dialogWindowInsetsController =
WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView)

if (isEdgeToEdgeFeatureFlagOn) {
if (isEdgeToEdge) {
activityWindowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
dialogWindowInsetsController.systemBarsBehavior =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1377,7 +1377,7 @@ internal object TextLayoutManager {
return FontMetricsUtil.getFontMetrics(
layout.text,
layout,
DisplayMetricsHolder.getWindowDisplayMetrics(),
DisplayMetricsHolder.getScreenDisplayMetrics(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK, as it's only used for screen density.

)
}

Expand Down
Loading
Loading