[PR #231] Support edge-to-edge and target Android 16 #235

Open
opened 2026-10-29 12:17:17 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/cylonid/NativeAlphaForAndroid/pull/231
Author: @dkodr
Created: 9/11/2026
Status: 🔄 Open

Base: dev ← Head: feat/edge-to-edge-target-36


📝 Commits (10+)

  • 0df7b8b Support edge-to-edge and target Android 16
  • 6038466 Blend the system bars into the web page
  • 14848da Tint the toolbar icons for the signature background
  • 2c00511 Even out the settings margins and free the scrollbar
  • 523bb0d Make the web app list and the settings actions read as Material
  • 46476ab Give the settings actions a Material hierarchy
  • 2e253fa Generate both color schemes from the same seed
  • 983bc9b Take the swipe action colors from the scheme
  • bcd6675 Colour each bar area from the part of the page behind it
  • f8e7806 Tidy up after review

📊 Changes

23 files changed (+402 additions, -224 deletions)

View changed files

📝 app/build.gradle (+2 -2)
📝 app/src/main/AndroidManifest.xml (+1 -1)
📝 app/src/main/java/com/cylonid/nativealpha/AboutActivity.kt (+2 -0)
📝 app/src/main/java/com/cylonid/nativealpha/MainActivity.kt (+2 -0)
📝 app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java (+33 -19)
➕ app/src/main/java/com/cylonid/nativealpha/util/WindowInsetsUtils.kt (+124 -0)
📝 app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt (+2 -0)
📝 app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt (+2 -0)
📝 app/src/main/res/layout/activity_main.xml (+6 -2)
📝 app/src/main/res/layout/fragment_adblock_list.xml (+1 -1)
📝 app/src/main/res/layout/fragment_web_app_list.xml (+2 -2)
📝 app/src/main/res/layout/global_settings.xml (+13 -5)
📝 app/src/main/res/layout/toolbar_back_action.xml (+2 -1)
📝 app/src/main/res/layout/web_app_list_item.xml (+4 -5)
📝 app/src/main/res/layout/webapp_settings.xml (+15 -4)
📝 app/src/main/res/values-night/colors.xml (+120 -120)
📝 app/src/main/res/values-night/themes.xml (+8 -3)
📝 app/src/main/res/values/colors.xml (+49 -53)
📝 app/src/main/res/values/dimens.xml (+1 -0)
📝 app/src/main/res/values/theme_overlays.xml (+3 -0)

...and 3 more files

📄 Description

Android 16 deprecates and disables windowOptOutEdgeToEdgeEnforcement
(behaviour changes), so the opt-out added in e5f97b3 for the Android 15
enforcement stops working the moment the target is raised. Since 31 August 2026
Google Play requires new submissions and updates to target API 36, so raising it
is no longer optional. This handles the insets instead of opting out.

Closes #171. Also fixes #193 and #194. It supersedes #216 and covers the same
ground as #217, which I only found after starting — thanks to @tedgravlin for
both; the back-navigation part here migrates to OnBackPressedDispatcher rather
than only enabling the callback, because at target 36 onBackPressed() is no
longer called at all.

Target and toolchain

  • compileSdk and targetSdk to 36; AGP to 8.13.2 and Gradle to 8.13, the
    minimum that supports API 36. Deliberately still on the 8.x line, since the
    manifest-rewriting build logic does not survive AGP 9.
  • versionCode and versionName are untouched — that is your release step.

Edge-to-edge

  • Removed the opt-out and the hardcoded status bar colours.
  • Content views are padded by systemBars | displayCutout | ime and report those
    types back as Insets.NONE, per the WebView inset guidance, so the
    WebView does not pad them a second time.
  • App bars draw behind the status bar through fitsSystemWindows.
  • setDecorFitsSystemWindows(false) plus transparent bar colours for API 28–34,
    where removing the opt-out alone does nothing.
  • Immersive mode pads by the IME only, so web content reaches the cutout again.
    That is #193.
  • WebView back navigation moved to OnBackPressedDispatcher (#194 is the
    keyboard part; this is the predictive-back part).

Bars that match the page

Padding the WebView leaves two strips showing the window background, which does
not match the page behind them. After each load the page reports its
theme-color and its computed body background; the first fills the top strip,
the second the bottom, and each bar takes its icon colour from its own side.

One sampled colour is not enough: on a page with a dark header over light
content the mismatched end was off by 224 of 255 per channel. Measured on three
pages, comparing theme-color for both strips, body background for both, and one
each:

page theme-color ×2 body ×2 one each
uniform white 0 / 0 0 / 0 0 / 0
Wikipedia 0 / 19 21 / 10 0 / 10
dark header, light body 0 / 224 224 / 0 0 / 0

Pages that do not handle safe-area insets themselves work the same as any
other, which is why this does not rely on env(safe-area-inset-*).

Visual cleanup

These are separate commits and can be dropped if you would rather keep this PR
to the release blocker.

  • Both colour schemes now come from one seed. The light scheme matched a
    Material tonal-spot scheme seeded with #B71C1C in 92 of 141 values; the dark
    one matched in 21, and its primary was #F0BE6D, an amber from an unrelated
    seed — hence amber switches under a red app bar in dark mode. Both are
    regenerated with material-color-utilities:
    new SchemeTonalSpot(Hct.fromInt(argbFromHex('#B71C1C')), isDark, contrast).
    This does change every dark-mode colour, so it is worth a look.
  • Overflow icon contrast 1.42:1 → 6.57:1; it inherited a dark
    colorControlNormal on the red app bar, below the 3:1 WCAG needs for
    interface icons.
  • Swipe action colours were hardcoded outside the theme: #EF6C00 next to
    the signature red, and #FD2C2C for delete, a third red. Now
    colorErrorContainer and colorTertiaryContainer, which follow the theme and
    have dark variants.
  • Settings margins were 25dp on the left and 45dp on the right, and the
    outsideOverlay scrollbar floated 25dp from the screen edge.
  • List titles were upper-cased and centred inside a weighted column that
    starts after the drag handle, which left them visibly off-centre.
  • Settings actions sat flush against the last setting; two neutral text
    buttons are also no longer a recommended pairing, so Save is filled now.
  • Removed colorSignatureDark and swipe_action_positive, both unreferenced.

Testing

Pixel on Android 17 (API 37, WebView 151) and an API 34 emulator, light and dark
system themes: main screen, settings, a light and a dark page, immersive on and
off, back through WebView history → base URL → background, and a page with a
bottom-anchored input and a sticky bottom bar with the keyboard open.

Not tested: landscape, cutouts on the short edges, and sw >= 600dp, where
target 36 additionally stops honouring the portrait lock.

RoboTests.initializationError and UtilUnitTests.faviconFromWebManifest fail
identically on unmodified dev — the first has no runnable methods, the second
asserts against a live favicon URL that has changed.

Two notes on the build

libadblock-client.so from Edsuns/AdblockAndroid:ad-filter:v0.9.1 has 4 KB
LOAD alignment in all four ABIs. Android 17 shows a compatibility warning
naming it, and since 1 November 2025 Play rejects apps targeting Android 15+
whose native code is not 16 KB aligned. The library has had no release since
v0.9.1, so this needs a rebuilt or replaced dependency — independent of this PR,
but it will block the same release.

Also, preBuild.dependsOn renameManifest moves AndroidManifest.xml out of the
way and only assemble's doLast moves it back, so two assemble tasks in one
Gradle invocation delete it, and a failed build leaves the tree dirty. Worth a
line in the README until it changes.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cylonid/NativeAlphaForAndroid/pull/231 **Author:** [@dkodr](https://github.com/dkodr) **Created:** 9/11/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `feat/edge-to-edge-target-36` --- ### 📝 Commits (10+) - [`0df7b8b`](https://github.com/cylonid/NativeAlphaForAndroid/commit/0df7b8bdb89bbc2b07cac1f69d6fd069a7c3315e) Support edge-to-edge and target Android 16 - [`6038466`](https://github.com/cylonid/NativeAlphaForAndroid/commit/60384666aa486648c7e21201031990a153fcf96f) Blend the system bars into the web page - [`14848da`](https://github.com/cylonid/NativeAlphaForAndroid/commit/14848dad945316025babc566a1b36838abd182ec) Tint the toolbar icons for the signature background - [`2c00511`](https://github.com/cylonid/NativeAlphaForAndroid/commit/2c00511da81d5944a0723084a71e95979998f298) Even out the settings margins and free the scrollbar - [`523bb0d`](https://github.com/cylonid/NativeAlphaForAndroid/commit/523bb0d9a4e1afa99b4253baeb6f1381052d6502) Make the web app list and the settings actions read as Material - [`46476ab`](https://github.com/cylonid/NativeAlphaForAndroid/commit/46476abd51098010ddda4586b191fe39f45d81a1) Give the settings actions a Material hierarchy - [`2e253fa`](https://github.com/cylonid/NativeAlphaForAndroid/commit/2e253fa298f4dcf5e827fdc336edbd3d0f6daf14) Generate both color schemes from the same seed - [`983bc9b`](https://github.com/cylonid/NativeAlphaForAndroid/commit/983bc9bb0f00f59274705da18bbd1df85e1c1cdb) Take the swipe action colors from the scheme - [`bcd6675`](https://github.com/cylonid/NativeAlphaForAndroid/commit/bcd66755a445025b0335069cc1c7afc347c0e3f7) Colour each bar area from the part of the page behind it - [`f8e7806`](https://github.com/cylonid/NativeAlphaForAndroid/commit/f8e780604b3038b9af29ebe8af57c460da83e09e) Tidy up after review ### 📊 Changes **23 files changed** (+402 additions, -224 deletions) <details> <summary>View changed files</summary> 📝 `app/build.gradle` (+2 -2) 📝 `app/src/main/AndroidManifest.xml` (+1 -1) 📝 `app/src/main/java/com/cylonid/nativealpha/AboutActivity.kt` (+2 -0) 📝 `app/src/main/java/com/cylonid/nativealpha/MainActivity.kt` (+2 -0) 📝 `app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java` (+33 -19) ➕ `app/src/main/java/com/cylonid/nativealpha/util/WindowInsetsUtils.kt` (+124 -0) 📝 `app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt` (+2 -0) 📝 `app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt` (+2 -0) 📝 `app/src/main/res/layout/activity_main.xml` (+6 -2) 📝 `app/src/main/res/layout/fragment_adblock_list.xml` (+1 -1) 📝 `app/src/main/res/layout/fragment_web_app_list.xml` (+2 -2) 📝 `app/src/main/res/layout/global_settings.xml` (+13 -5) 📝 `app/src/main/res/layout/toolbar_back_action.xml` (+2 -1) 📝 `app/src/main/res/layout/web_app_list_item.xml` (+4 -5) 📝 `app/src/main/res/layout/webapp_settings.xml` (+15 -4) 📝 `app/src/main/res/values-night/colors.xml` (+120 -120) 📝 `app/src/main/res/values-night/themes.xml` (+8 -3) 📝 `app/src/main/res/values/colors.xml` (+49 -53) 📝 `app/src/main/res/values/dimens.xml` (+1 -0) 📝 `app/src/main/res/values/theme_overlays.xml` (+3 -0) _...and 3 more files_ </details> ### 📄 Description Android 16 deprecates and disables `windowOptOutEdgeToEdgeEnforcement` ([behaviour changes][1]), so the opt-out added in e5f97b3 for the Android 15 enforcement stops working the moment the target is raised. Since 31 August 2026 Google Play requires new submissions and updates to target API 36, so raising it is no longer optional. This handles the insets instead of opting out. Closes #171. Also fixes #193 and #194. It supersedes #216 and covers the same ground as #217, which I only found after starting — thanks to @tedgravlin for both; the back-navigation part here migrates to `OnBackPressedDispatcher` rather than only enabling the callback, because at target 36 `onBackPressed()` is no longer called at all. [1]: https://developer.android.com/about/versions/16/behavior-changes-16#edge-to-edge ## Target and toolchain - `compileSdk` and `targetSdk` to 36; AGP to 8.13.2 and Gradle to 8.13, the minimum that supports API 36. Deliberately still on the 8.x line, since the manifest-rewriting build logic does not survive AGP 9. - `versionCode` and `versionName` are untouched — that is your release step. ## Edge-to-edge - Removed the opt-out and the hardcoded status bar colours. - Content views are padded by `systemBars | displayCutout | ime` and report those types back as `Insets.NONE`, per the [WebView inset guidance][2], so the WebView does not pad them a second time. - App bars draw behind the status bar through `fitsSystemWindows`. - `setDecorFitsSystemWindows(false)` plus transparent bar colours for API 28–34, where removing the opt-out alone does nothing. - Immersive mode pads by the IME only, so web content reaches the cutout again. That is #193. - WebView back navigation moved to `OnBackPressedDispatcher` (#194 is the keyboard part; this is the predictive-back part). [2]: https://developer.android.com/develop/ui/views/layout/webapps/understand-window-insets ## Bars that match the page Padding the WebView leaves two strips showing the window background, which does not match the page behind them. After each load the page reports its `theme-color` and its computed `body` background; the first fills the top strip, the second the bottom, and each bar takes its icon colour from its own side. One sampled colour is not enough: on a page with a dark header over light content the mismatched end was off by 224 of 255 per channel. Measured on three pages, comparing theme-color for both strips, body background for both, and one each: | page | theme-color ×2 | body ×2 | one each | |---|---|---|---| | uniform white | 0 / 0 | 0 / 0 | 0 / 0 | | Wikipedia | 0 / 19 | 21 / 10 | **0 / 10** | | dark header, light body | 0 / 224 | 224 / 0 | **0 / 0** | Pages that do not handle safe-area insets themselves work the same as any other, which is why this does not rely on `env(safe-area-inset-*)`. ## Visual cleanup These are separate commits and can be dropped if you would rather keep this PR to the release blocker. - **Both colour schemes now come from one seed.** The light scheme matched a Material tonal-spot scheme seeded with `#B71C1C` in 92 of 141 values; the dark one matched in 21, and its primary was `#F0BE6D`, an amber from an unrelated seed — hence amber switches under a red app bar in dark mode. Both are regenerated with `material-color-utilities`: `new SchemeTonalSpot(Hct.fromInt(argbFromHex('#B71C1C')), isDark, contrast)`. This does change every dark-mode colour, so it is worth a look. - **Overflow icon contrast** 1.42:1 → 6.57:1; it inherited a dark `colorControlNormal` on the red app bar, below the 3:1 WCAG needs for interface icons. - **Swipe action colours** were hardcoded outside the theme: `#EF6C00` next to the signature red, and `#FD2C2C` for delete, a third red. Now `colorErrorContainer` and `colorTertiaryContainer`, which follow the theme and have dark variants. - **Settings margins** were 25dp on the left and 45dp on the right, and the `outsideOverlay` scrollbar floated 25dp from the screen edge. - **List titles** were upper-cased and centred inside a weighted column that starts after the drag handle, which left them visibly off-centre. - **Settings actions** sat flush against the last setting; two neutral text buttons are also no longer a recommended pairing, so Save is filled now. - Removed `colorSignatureDark` and `swipe_action_positive`, both unreferenced. ## Testing Pixel on Android 17 (API 37, WebView 151) and an API 34 emulator, light and dark system themes: main screen, settings, a light and a dark page, immersive on and off, back through WebView history → base URL → background, and a page with a bottom-anchored input and a sticky bottom bar with the keyboard open. Not tested: landscape, cutouts on the short edges, and `sw >= 600dp`, where target 36 additionally stops honouring the portrait lock. `RoboTests.initializationError` and `UtilUnitTests.faviconFromWebManifest` fail identically on unmodified `dev` — the first has no runnable methods, the second asserts against a live favicon URL that has changed. ## Two notes on the build `libadblock-client.so` from `Edsuns/AdblockAndroid:ad-filter:v0.9.1` has 4 KB `LOAD` alignment in all four ABIs. Android 17 shows a compatibility warning naming it, and [since 1 November 2025][3] Play rejects apps targeting Android 15+ whose native code is not 16 KB aligned. The library has had no release since v0.9.1, so this needs a rebuilt or replaced dependency — independent of this PR, but it will block the same release. Also, `preBuild.dependsOn renameManifest` moves `AndroidManifest.xml` out of the way and only `assemble`'s `doLast` moves it back, so two `assemble` tasks in one Gradle invocation delete it, and a failed build leaves the tree dirty. Worth a line in the README until it changes. [3]: https://android-developers.googleblog.com/2025/05/prepare-play-apps-for-devices-with-16kb-page-size.html --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cylonid/NativeAlphaForAndroid#235
No description provided.