fix(android): use the context's display for display metrics, not the device default#56895
fix(android): use the context's display for display metrics, not the device default#56895DouweBos wants to merge 1 commit into
Conversation
|
Hi @DouweBos! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
…device default DisplayMetricsHolder.initDisplayMetrics populated screenDisplayMetrics by calling getRealMetrics on WindowManager.defaultDisplay, which is always the device's primary display regardless of which display the activity is running on. On a secondary display (Samsung DeX, desktop mode, external monitor, freeform window) that reports the primary display's density and dimensions, so PixelUtil's dp <-> px conversion — and therefore Fabric's layout — scales content for the wrong display. The visible region ends up clipped to a fraction of the activity window and text renders at sub-pixel positions. Use Context.getDisplay() (API 30+) so the metrics come from the display the context is actually associated with, falling back to defaultDisplay on older API levels. Also pass the view's own context (not the application context) from ReactRootView, so the context is associated with the activity's display. Fixes facebook#56894 (also tracked in facebook#55659). Changelog: [ANDROID] [FIXED] - Display metrics now reflect the activity's display instead of the device's default display, fixing layout scaling on secondary displays / desktop mode / freeform windows.
eeaa7cd to
aa34674
Compare
|
Sorry, incorrectly assumed the original |
Summary:
DisplayMetricsHolder.initDisplayMetricspopulatesscreenDisplayMetricsby callinggetRealMetricsonWindowManager.defaultDisplay.defaultDisplayis always the device's primary display, regardless of which display the activity is actually running on.When an activity runs on a secondary display (Samsung DeX, desktop mode an external monitor, a freeform window, or an emulator virtual display via
am start --display N) its density and dimensions differ from the primary's.PixelUtil(toPixelFromDIP/toDIPFromPixel/toPixelFromSP/getDisplayMetricDensity) readsscreenDisplayMetricsfor every dp <-> px conversion the layout engine performs, so Fabric lays out content at the primary display's scale into a window that doesn't have that scale. The visible content is clipped to a fraction of the activity window with the rest left black, and text renders at sub-pixel positions (blurry).This is observable from JS —
Dimensions.get('window')anduseWindowDimensions()are correct, butDimensions.get('screen')reports the primary display'sscale:This PR changes
initDisplayMetricsto read from the display theContextis associated with,Context.getDisplay()on API 30+, falling back toWindowManager.defaultDisplayon older API levels, instead of always the device default. It also passes the view's own context (not the application context) fromReactRootView, so that context is associated with the activity's display.Single-display behavior is unchanged: when the activity is on the primary display, the context's display is the default display, so the metrics are identical.
Fixes #56894 (also tracked in #55659).
Changelog:
[ANDROID] [FIXED] - Display metrics now reflect the activity's display instead of the device's default display, fixing layout scaling on secondary displays / desktop mode / freeform windows.
Test Plan:
Reproducer (standalone minimal app + commands): https://github.com/DouweBos/rn-secondary-display-repro
Unit tests
./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.uimanager.DisplayMetricsHolderTest" --tests "com.facebook.react.uimanager.PixelUtilTest"A/B on
rn-tester, built from sourceBuilt
rn-testerfrom this branch and from its parent commit (this commit reverted), installed both on the same Pixel 9 Pro AVD (API 36) with a virtual secondary display at a different density than the primary:Before (parent commit) —
screenDisplayMetricstaken from the primary display; the dp ↔ px conversion uses the primary's 3.0x density on a 1.5x display, so text is clipped/garbled ("Comp" instead of "Components", "Bu", "DrawerLay", …) and the bottom tab indicator is sized for the wrong width:After (this branch) — metrics come from the activity's display; layout fills the window and all labels render fully and crisply: