android: add optional native host lifecycle hooks

This commit is contained in:
Codex Agent
2026-08-12 17:41:26 -05:00
parent c3136bf8f7
commit ee00728e9e
2 changed files with 89 additions and 6 deletions
@@ -143,16 +143,36 @@ public class GameActivity extends SDLActivity {
private static native void nativeSetDefaultStreamValues(int sampleRate, int framesPerBurst); private static native void nativeSetDefaultStreamValues(int sampleRate, int framesPerBurst);
/**
* Native libraries required by an optional Android host extension.
*
* Subclasses supplied by another product flavor may override this method.
* The libraries are loaded after LÖVE's dependencies and before liblove;
* liblove must remain last because SDL treats the final entry as the main
* shared object.
*/
protected String[] getHostLibraries() {
return new String[0];
}
@Override @Override
protected String[] getLibraries() { protected String[] getLibraries() {
return new String[] { String[] hostLibraries = getHostLibraries();
"c++_shared", String[] libraries = new String[hostLibraries.length + 4];
"mpg123", libraries[0] = "c++_shared";
"openal", libraries[1] = "mpg123";
"love", libraries[2] = "openal";
}; System.arraycopy(hostLibraries, 0, libraries, 3, hostLibraries.length);
libraries[libraries.length - 1] = "love";
return libraries;
} }
protected void onHostCreateBeforeSDL(Bundle savedInstanceState) {}
protected void onHostCreateAfterSDL(Bundle savedInstanceState) {}
protected void onHostResume() {}
protected void onHostPause() {}
protected void onHostDestroy() {}
@Override @Override
protected String getMainSharedObject() { protected String getMainSharedObject() {
String[] libs = getLibraries(); String[] libs = getLibraries();
@@ -192,7 +212,9 @@ public class GameActivity extends SDLActivity {
intent.setData(null); intent.setData(null);
} }
onHostCreateBeforeSDL(savedInstanceState);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
onHostCreateAfterSDL(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
// Restore the in-flight SAF destinations, so a pick that returns to // Restore the in-flight SAF destinations, so a pick that returns to
// a recreated activity still lands under the basename it asked for. // a recreated activity still lands under the basename it asked for.
@@ -341,6 +363,7 @@ public class GameActivity extends SDLActivity {
Log.d("GameActivity", "Cancelling vibration"); Log.d("GameActivity", "Cancelling vibration");
vibrator.cancel(); vibrator.cancel();
} }
onHostDestroy();
super.onDestroy(); super.onDestroy();
} }
@@ -351,12 +374,14 @@ public class GameActivity extends SDLActivity {
vibrator.cancel(); vibrator.cancel();
} }
teardownSecondaryDisplay(); teardownSecondaryDisplay();
onHostPause();
super.onPause(); super.onPause();
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
onHostResume();
setupSecondaryDisplay(); setupSecondaryDisplay();
} }
@@ -0,0 +1,58 @@
-- GameActivity exposes an optional Android host seam without requiring any
-- host implementation (or its native libraries) in the stock build.
-- Self-contained: luajit tests/engine/android_host_extension_test.lua
local path = "mobile/android/love/src/main/java/org/love2d/android/GameActivity.java"
local file = assert(io.open(path, "rb"))
local source = file:read("*a")
file:close()
local function check(value, message)
if not value then error(message, 2) end
end
local function position(text)
local p = source:find(text, 1, true)
check(p, "missing: " .. text)
return p
end
check(source:find("protected String[] getHostLibraries()", 1, true),
"host libraries are an overridable protected extension")
check(source:find("return new String[0];", 1, true),
"the vanilla host library list is empty")
local cpp = position('libraries[0] = "c++_shared";')
local mpg = position('libraries[1] = "mpg123";')
local openal = position('libraries[2] = "openal";')
local host = position("System.arraycopy(hostLibraries, 0, libraries, 3, hostLibraries.length);")
local love = position('libraries[libraries.length - 1] = "love";')
check(cpp < mpg and mpg < openal and openal < host and host < love,
"optional libraries load after dependencies while liblove remains last")
for _, hook in ipairs({
"onHostCreateBeforeSDL", "onHostCreateAfterSDL", "onHostResume",
"onHostPause", "onHostDestroy",
}) do
check(source:find("protected void " .. hook, 1, true),
hook .. " is a protected extension hook")
end
check(position("onHostCreateBeforeSDL(savedInstanceState);") <
position("super.onCreate(savedInstanceState);") and
position("super.onCreate(savedInstanceState);") <
position("onHostCreateAfterSDL(savedInstanceState);"),
"create hooks bracket SDL creation")
check(position("super.onResume();") < position("onHostResume();"),
"resume hook runs after SDL resumes")
check(position("onHostPause();") < position("super.onPause();"),
"pause hook runs before SDL pauses")
check(position("onHostDestroy();") < position("super.onDestroy();"),
"destroy hook runs before SDL destruction")
check(not source:lower():find("openxr", 1, true),
"generic Android activity must not require OpenXR")
check(not source:find("QuestActivity", 1, true) and
not source:find("QuestBridge", 1, true),
"generic Android activity must not require Quest classes")
print("android_host_extension_test: ok")