Skip to content

android: Follow the system night mode on the host-driven path - #30

Open
Bombatomica64 wants to merge 1 commit into
longbridge:mainfrom
Bombatomica64:android-host-night-mode
Open

Bombatomica64 wants to merge 1 commit into
longbridge:mainfrom
Bombatomica64:android-host-night-mode

Conversation

@Bombatomica64

@Bombatomica64 Bombatomica64 commented Oct 9, 2026 •

Copy link
Copy Markdown

On the host-driven entry point (android::host), the GPUI window does not follow the system's dark mode. This PR reads night mode from the host Activity and applies it at the points where it can change.

Problem

  • query_night_mode_via_jni() reads the configuration through AndroidApp's AssetManager. The host-driven path has no AndroidApp, so the window stayed Light whatever the system used.
  • Platform::window_appearance always answered Dark, on either path.

Change

  • Without an AndroidApp, night mode is read from the application context's getResources().getConfiguration().uiMode. The first set_host_activity keeps that context for the life of the process. Its configuration follows system-wide changes whatever the Activity in front handles itself, whereas an Activity that lists uiMode in configChanges keeps its creation-time configuration in its AssetManager.
  • The host path applies the system appearance when a window first opens, when a recreated Activity's surface is attached, and on resumed (the mode may have changed while another app was in front).
  • New host::configuration_changed(). An Activity that handles uiMode itself must call it from onConfigurationChanged, otherwise its window keeps the old mode until it is resumed. It syncs every host's window, so stacked Activities follow too. Like MainEvent::ConfigChanged on the android-activity path, it also calls notify_keyboard_layout_change, so that now runs on every configuration change the host forwards.
  • window_appearance returns the same value.

The android-activity path reads night mode as before; only window_appearance changes there.

Screenshots

The system switched to dark while the app was open. Left: without this change, the window stays Light. Right: with it. (OnePlus CPH2581, Android 16, host-driven test app.)

night mode before / after

Testing

  • cargo fmt --all -- --check, cargo clippy --target aarch64-linux-android --lib: no new warnings.
  • On a OnePlus CPH2581 (Android 16), with a host Activity that lists uiMode in configChanges: cold start in light and in dark; switching to dark while the app is in front (configuration_changed); and switching back to light while it is in the background, then returning to it. In that last case Android delivers onConfigurationChanged just before onResume, so the resumed sync is there for hosts that do not forward configuration changes.
  • Before the change, on the same device, the window stayed Light after switching to dark.

Test app: Bombatomica64/Gpui-android (its Diagnostics screen shows the window and platform appearance).

The emoji font on the host path is a separate PR (#29), since it does not depend on this one.

🤖 Generated with Claude Code

Bombatomica64 added a commit to Bombatomica64/Gpui-android that referenced this pull request Oct 9, 2026
…#22)

Fork main 3d74121 merges the revised android-host-night-mode (c62c318) and
android-host-emoji-font (77126f2), which longbridge/gpui-mobile#30 and #29
will carry: night mode is read from the application context, and the
emoji font's AssetManager is kept for the life of the process.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@Bombatomica64
Bombatomica64 force-pushed the android-host-night-mode branch from 8da05ca to c62c318 Compare October 9, 2026 10:33

@huacnlee huacnlee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for following up on the night-mode note from #29. A few things to fix before merging. These come from reading the diff; I have not checked them on a device.

1. The android-activity path still starts Light, and now disagrees with the platform (jni.rs, InitWindow branch)
Platform::window_appearance() now returns the real night mode, but the android-activity InitWindow path (platform.open_window and the init_window reattach) never calls sync_appearance. On a cold start in dark mode, cx.window_appearance() says Dark while the window's appearance() stays Light until a ConfigChanged, and observe_window_appearance never fires. Before this PR both paths at least agreed on Dark; now they disagree.

2. Sync the initial appearance in the shared code, not only at host.rs call sites (host.rs)
The initial sync is added in three host-only places (new window, reattach, resume). Calling sync_appearance inside AndroidPlatform::open_window (and after init_window) would fix both entry points in one place and resolve item 1.

3. The application context ignores app-local night mode (jni.rs, host_ui_mode)
Night mode is read from getApplicationContext().getResources().getConfiguration(). An app-local setting such as AppCompatDelegate.setDefaultNightMode changes only the Activity's configuration, so an app with an in-app dark-theme toggle would be dark everywhere except the GPUI window. Please read the Activity's Resources instead.

4. configuration_changed depends on the order Android delivers config changes in (host.rs)
Inside onConfigurationChanged, the code reads the application's configuration, assuming the process-level change has already been applied. Process and Activity configuration changes are separate transactions, so this can read a stale uiMode. Passing newConfig.uiMode from the callback into configuration_changed would remove that dependency.

Non-blocking:

  • window_appearance() now makes several JNI calls on every call on the host path, and builds a new AConfiguration each time on the android-activity path. Theme code that calls it during render pays that cost every frame. Returning the appearance already stored by sync_appearance would cost nothing and stay consistent with PlatformWindow::appearance.
  • The ConfigurationChanged handler runs the same process-wide query once per window. Query once before the loop and call set_appearance on each window.
  • host_ui_mode errors are swallowed by is_ok_and, so a failed context capture silently means Light forever. Please log a warning.

The host-driven entry point has no AndroidApp, and night mode was only read
through its AssetManager, so a host-driven window stayed Light whatever the
system used. Platform::window_appearance always answered Dark on either
path.

Night mode is now read from the application context's Resources when there
is no AndroidApp. The first set_host_activity keeps that context for the
life of the process. Its configuration follows system-wide changes whatever
the Activity in front handles itself, whereas an Activity that handles
uiMode keeps a stale configuration in its AssetManager. The host path
applies it when a window first opens, when a recreated Activity's surface
is attached, on resume, and, for every host's window, from the new
host::configuration_changed(), which an Activity that handles uiMode must
call from onConfigurationChanged. window_appearance reports the same night
mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Bombatomica64
Bombatomica64 force-pushed the android-host-night-mode branch from c62c318 to ec5c89d Compare October 12, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants