diff --git a/src/ir/jni_java_emit.test.zig b/src/ir/jni_java_emit.test.zig index 36b94cf..b5fad9f 100644 --- a/src/ir/jni_java_emit.test.zig +++ b/src/ir/jni_java_emit.test.zig @@ -50,7 +50,7 @@ test "rejects non-main decl" { try std.testing.expectError(emit.EmitError.NotAJniMainClass, result); } -test "void onCreate(Bundle) with #extends NativeActivity default" { +test "void onCreate(Bundle) with default Activity superclass" { const a = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(a); defer arena.deinit(); @@ -85,7 +85,7 @@ test "void onCreate(Bundle) with #extends NativeActivity default" { const expected = \\package co.swipelab.sx_runtime; \\ - \\public class SxNativeActivity extends android.app.NativeActivity { + \\public class SxNativeActivity extends android.app.Activity { \\ @Override \\ public void onCreate(android.os.Bundle b) { \\ super.onCreate(b); diff --git a/src/ir/jni_java_emit.zig b/src/ir/jni_java_emit.zig index 8f2fd9d..6e703ef 100644 --- a/src/ir/jni_java_emit.zig +++ b/src/ir/jni_java_emit.zig @@ -43,7 +43,10 @@ pub const Options = struct { /// Used to resolve `*Foo` cross-class refs in method signatures. classes: ?*const std.StringHashMap([]const u8) = null, /// Default superclass when the user doesn't write `#extends ...;`. - default_extends: []const u8 = "android.app.NativeActivity", + /// `android.app.Activity` is the standard base for Java-driven + /// Activities — `NativeActivity` is the legacy NDK path that + /// requires native_app_glue's `ANativeActivity_onCreate`. + default_extends: []const u8 = "android.app.Activity", }; /// Emit a `.java` source for the given foreign-class decl. Result is diff --git a/src/target.zig b/src/target.zig index b47a95e..64b40d4 100644 --- a/src/target.zig +++ b/src/target.zig @@ -430,13 +430,20 @@ pub fn createApk(allocator: std.mem.Allocator, io: std.Io, so_path: []const u8, const so_dest = try std.fs.path.join(allocator, &.{ lib_dir, so_basename }); cwd.copyFile(so_path, cwd, so_dest, io, .{}) catch return error.ApkStageFailed; - // Manifest: either user-supplied or auto-generated. + // Manifest: either user-supplied or auto-generated. When a `#jni_main` + // class is declared, the auto-generated manifest points its + // `` at the user's class and flips + // `android:hasCode="true"` so Android loads the bundled classes.dex. + // Otherwise we fall back to the legacy NativeActivity shape. const manifest_path = if (target_config.manifest_path) |mp| try allocator.dupe(u8, mp) else blk: { const generated = try std.fmt.allocPrint(allocator, "{s}/AndroidManifest.xml", .{stage}); const lib_name = libNameFromSoBasename(so_basename); - const manifest_xml = try buildAndroidManifest(allocator, bundle_id, lib_name); + const manifest_xml = if (jni_main_decls.len > 0) + try buildJniMainManifest(allocator, bundle_id, lib_name, jni_main_decls[0].foreign_path) + else + try buildAndroidManifest(allocator, bundle_id, lib_name); try cwd.writeFile(io, .{ .sub_path = generated, .data = manifest_xml }); break :blk generated; }; @@ -514,6 +521,42 @@ fn libNameFromSoBasename(basename: []const u8) []const u8 { return name; } +/// Manifest for a `#jni_main` Activity: `` points +/// at the user's class, `android:hasCode="true"` so the bundled +/// classes.dex is loaded, and the `android.app.lib_name` meta-data is +/// dropped (that's a NativeActivity-only mechanism — Java-driven +/// Activities load the .so via `System.loadLibrary` from a static +/// initializer the Java emitter will synthesize once slice R.3 lands). +fn buildJniMainManifest(allocator: std.mem.Allocator, package: []const u8, lib_name: []const u8, foreign_path: []const u8) ![]const u8 { + var class_name = std.ArrayList(u8).empty; + for (foreign_path) |ch| { + try class_name.append(allocator, if (ch == '/') '.' else ch); + } + const activity_name = try class_name.toOwnedSlice(allocator); + return std.fmt.allocPrint(allocator, + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + , .{ package, lib_name, activity_name, lib_name }); +} + fn buildAndroidManifest(allocator: std.mem.Allocator, package: []const u8, lib_name: []const u8) ![]const u8 { return std.fmt.allocPrint(allocator, \\