Skip to content

Modernise build system: NDK r30, AGP 9.1.1, targetSdk 36, Android 15 fixes#4

Closed
robmat wants to merge 16 commits intoMeridianOXC:oxce-plus-protofrom
robmat:upgrade_agp_gradle_ndk_etc
Closed

Modernise build system: NDK r30, AGP 9.1.1, targetSdk 36, Android 15 fixes#4
robmat wants to merge 16 commits intoMeridianOXC:oxce-plus-protofrom
robmat:upgrade_agp_gradle_ndk_etc

Conversation

@robmat
Copy link
Copy Markdown

@robmat robmat commented Apr 23, 2026

Summary

Brings the build system and Android runtime code up to date so the project builds with the current stable toolchain and runs correctly on Android 11–15.

Only tested by playing the game daily on a physical Android device. No automated tests were run.


Build system

What Before After
Gradle 4.10.3 9.3.1
Android Gradle Plugin 3.3.1 9.1.1
NDK 25.1.8937393 r30 (30.0.14904198)
compileSdk 28 35
targetSdk 28 36
minSdk 16 21

Other housekeeping: switched from jcenter() to mavenCentral(), pinned the NDK version explicitly, replaced the grgit revision stamp with a plain git shell call, migrated ProGuard file to proguard-android-optimize.txt (required by AGP 8.13+).


NDK r30 compatibility (three issues)

1. libc++ 18 — removed char_traits specialisations (NDK r27+)

libc++ 18 (NDK r27+) removed non-standard char_traits specialisations for signed char and unsigned int. OpenXcom uses both (ModInfo.h, Unicode.h). A small compat header (libcxx18_char_traits_compat.h) is force-included for all C++ TUs via CMake.

2. -Wincompatible-function-pointer-types is now an error (NDK r28+)

SDL2's OpenGL ES2 renderer (SDL_gles2funcs.h) has a benign const-qualifier mismatch in its function pointer table. Suppressed for the SDL2 target only.

3. ALooper_pollAll removed (NDK r30)

ALooper_pollAll is marked __REMOVED_IN(1) in NDK r30 — hard compile error. Fixed by replacing it with ALooper_pollOnce (identical signature, same semantics for a zero-timeout poll) in the SDL submodule. That fix is tracked separately at StoddardOXC/SDL#1 — the SDL submodule pointer here depends on that PR being merged and the pointer updated before this lands.


Android 11+ storage (targetSdk 36)

WRITE_EXTERNAL_STORAGE has no effect on Android 11+ (API 30+) when targeting API 30+. PreloaderActivity needs broad write access to /sdcard/openxcom/ to extract bundled ZIPs at first launch. Added MANAGE_EXTERNAL_STORAGE permission with a runtime request flow that redirects the user to the system "All files access" settings screen on API 30+. Legacy WRITE_EXTERNAL_STORAGE runtime request is kept for API 23–29.

Android 15 fullscreen (targetSdk 35+)

Android 15 enforces edge-to-edge mode and ignores both Theme.NoTitleBar.Fullscreen and setSystemUiVisibility() for apps targeting API 35+. Since OpenXcom is always fullscreen, UiVisibilityChanger now unconditionally hides status and navigation bars via WindowInsetsController on API 30+, with BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE so a swipe can reveal them briefly. The legacy path is kept for API 11–29.

Android 12 manifest (compileSdk 35)

AGP enforces android:exported on any activity with an intent-filter when compileSdk >= 31. Added android:exported="true" to PreloaderActivity.


SDL submodule dependency

The SDL submodule currently points to a commit on robmat/SDL:ndk30-compat (the ALooper_pollAll fix, see StoddardOXC/SDL#1). Before this PR is merged, the submodule pointer should be updated to point to the merged upstream commit in StoddardOXC/SDL.

robmat and others added 16 commits April 23, 2026 12:47
Older Gradle/AGP versions fail with "Unable to get mutable Windows
environment variable map" on newer JDK/Windows versions. Upgrading
also pins the NDK version explicitly so the build is reproducible,
raises minSdk to 21 (drops pre-Lollipop support nobody uses), replaces
the deprecated jcenter() repository with mavenCentral(), and removes
the grgit dependency in favour of a plain git shell call for the
revision stamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AGP 8.x moves namespace out of AndroidManifest.xml into build.gradle,
restructures .iml files, and generates new Android Studio project
metadata. These are mechanical changes produced by the IDE on first
open after the Gradle upgrade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pulls in five fixes made to the submodule: null-check for empty-file
SDL_RWops, null guards in loadSpk/loadBdy/loadDat, null-frame skips
in the battlescape hair-bleach loops, and the corrected nativeSetPaths
JNI symbol. Together these prevent SIGSEGV and UnsatisfiedLinkError
crashes that occur when game data files are missing or corrupt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rties

These files are generated locally by Android Studio and the CMake/Gradle
build and should not be shared via version control as they contain
machine-specific paths and transient state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AGP 9.1.1 and Gradle 9.3.1 are the current stable toolchain versions.
The IDE project metadata (misc.xml) was regenerated by Android Studio
after the toolchain update.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NDK r30 is the latest stable release and includes Clang 18 / libc++ 18.
The three fixes in subsequent commits are required to build cleanly
against it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ions)

libc++ 18 (shipped with NDK r27+) removed non-standard char_traits
specializations for types other than char/wchar_t/char8_t/char16_t/
char32_t. OpenXcom depends on two removed specializations:

  - char_traits<signed char>   Engine/ModInfo.h: basic_string<signed char>
  - char_traits<unsigned int>  Engine/Unicode.h: basic_string<Uint32>

The compat header libcxx18_char_traits_compat.h provides full
specializations for both and is force-included at the top of every
C++ TU via the CMakeLists.txt -include flag so the types are visible
before any OpenXcom header that uses them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NDK r28+ promotes -Wincompatible-function-pointer-types to an error.
SDL2's OpenGL ES2 renderer (SDL_gles2funcs.h) uses function pointer
types whose const qualifiers differ from the system headers, triggering
this error. Suppress it for the SDL2 target only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or NDK r30

NDK r30 marks ALooper_pollAll with __REMOVED_IN(1, ...), making any
call to it a hard compile error. ALooper_pollOnce is the recommended
replacement with an identical signature. The zero-timeout non-blocking
poll used in SDL_ANDROID_SensorUpdate is semantically equivalent with
either call.

Committed on branch ndk30-compat of the SDL submodule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AGP 8.13 removed support for getDefaultProguardFile('proguard-android.txt')
because it includes -dontoptimize which blocks R8. The optimizing variant
is the correct default; minifyEnabled is false so R8 does not actually run
for release builds anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AGP 8.13 requires compileSdk >= 30 to compile Java 9+ language features
used by the updated toolchain. Targeting SDK 35 (Android 15) is current
best practice and required for Play Store submissions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iance

Android 12 (API 31) requires android:exported to be explicitly set on
any activity that declares an intent-filter. compileSdk 35 enforces
this at build time. PreloaderActivity is the LAUNCHER entry point so
exported=true is correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WRITE_EXTERNAL_STORAGE has no effect on Android 11+ (API 30+) for apps
targeting API 30+. PreloaderActivity needs broad write access to
/sdcard/openxcom/ when extracting bundled ZIPs at first launch.

On API 30+ use Environment.isExternalStorageManager() and redirect the
user to the system "All files access" settings screen via
ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION before proceeding.
On API 23-29 the existing WRITE_EXTERNAL_STORAGE runtime request is
kept. Below API 23 permission is granted implicitly as before.

MANAGE_EXTERNAL_STORAGE is declared in the manifest with
android:minSdkVersion="30" so it is only requested on devices that
recognise it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous targetSdk 28 was a temporary workaround to preserve legacy
external storage access. With the MANAGE_EXTERNAL_STORAGE permission and
runtime request now in place for Android 11+, it is safe to target the
current API level (Android 16).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Android 15 (targetSdk >= 35) enforces edge-to-edge mode and ignores
both Theme.NoTitleBar.Fullscreen and setSystemUiVisibility(). OpenXcom
is always a fullscreen game, so system bars must be hidden via the new
WindowInsetsController API on API 30+.

On API 30+: always hide status and navigation bars, with
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE so a swipe can reveal them
briefly. setDecorFitsSystemWindows(false) prevents the system from
reserving inset space for the bars.

On API 11-29: the existing setSystemUiVisibility() path is kept.

Also adds onWindowFocusChanged() in OpenXcom.java to re-apply the UI
state when the activity regains focus (e.g. returning from a dialog).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….1, targetSdk 36)

- Updated required versions: NDK r30 (30.0.14904198), compileSdk 35, targetSdk 36,
  Gradle 9.3.1, AGP 9.1.1
- Added NDK compatibility notes section covering libc++18 char_traits shim,
  NDK r28+ function pointer error suppression, and NDK r30 ALooper_pollAll removal
- Added Android 11+ MANAGE_EXTERNAL_STORAGE permission notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MeridianOXC
Copy link
Copy Markdown
Owner

Thank you very much.
We definitely need this.

I cannot look at this right now.
But I will return to it as soon as I can.

@MeridianOXC
Copy link
Copy Markdown
Owner

Slightly modified and merged manually.

@MeridianOXC MeridianOXC closed this May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants