ReviewOS

also looking at this

craft-native/craft

feat(android): getCurrentPosition in Zig — ratchet 52 → 51

#188
Merged glennmichael123 wants to merge feat/android-position into main
6 files +466 -3
packages/zig/src/android_dispatch.zigmodified+122-1
Changes to packages/zig/src/android_dispatch.zig
@@ -45,6 +45,7 @@ const network = ;
4545const appstate = ;
4646const bluetooth = ;
4747const motion = ;
48const position = ;
4849const json_number = ;
4950const securestore = ;
5051const haptics = ;
@@ -467,6 +468,23 @@ const natives = [_]jni.JNINativeMethod{
467468 .signature = "(Landroid/app/Activity;)Z",
468469 .fnPtr = @ptrCast(&nativeStopMotionUpdates),
469470 },
471 // Not actions: the two ends of the Play Services task listeners the
472 // holder owns.
473 .{
474 .name = "nativeLocationResult",
475 .signature = "(DDDDDDJ)V",
476 .fnPtr = @ptrCast(&nativeLocationResult),
477 },
478 .{
479 .name = "nativeLocationFailed",
480 .signature = "(Ljava/lang/String;)V",
481 .fnPtr = @ptrCast(&nativeLocationFailed),
482 },
483 .{
484 .name = "nativeGetCurrentPosition",
485 .signature = "(Landroid/app/Activity;)Z",
486 .fnPtr = @ptrCast(&nativeGetCurrentPosition),
487 },
470488};
471489
472490/// How binding went. A value rather than a log line, so every path is
@@ -2096,6 +2114,109 @@ fn callHolderInt(j: Jni, comptime name: [:0]const u8, activity: jni.jobject, val
20962114 );
20972115}
20982116
2117/// `nativeLocationResult(...)` — a fix, from either the cached or fresh path.
2118///
2119/// Every value arrives as a `double`, including the three the framework types
2120/// as `float`: the shim writes them with `put(name, value)`, and the overload
2121/// Kotlin picks is `put(String, double)` because widening beats boxing. So
2122/// they print as doubles, and the holder widens them for the same reason.
2123fn nativeLocationResult(
2124 env: jni.JNIEnv,
2125 _: jni.jobject,
2126 latitude: jni.jdouble,
2127 longitude: jni.jdouble,
2128 accuracy: jni.jdouble,
2129 altitude: jni.jdouble,
2130 speed: jni.jdouble,
2131 bearing: jni.jdouble,
2132 time: jni.jlong,
2133) callconv(.c) void {
2134 const j = Jni.init(env);
2135 var arena = std.heap.ArenaAllocator.init(backing);
2136 defer arena.deinit();
2137 const allocator = arena.allocator();
2138
2139 var timestamp: [24]u8 = undefined;
2140 const printed: position.Printed = .{
2141 .latitude = json_number.fromDouble(j, allocator, latitude) catch return,
2142 .longitude = json_number.fromDouble(j, allocator, longitude) catch return,
2143 .accuracy = json_number.fromDouble(j, allocator, accuracy) catch return,
2144 .altitude = json_number.fromDouble(j, allocator, altitude) catch return,
2145 .speed = json_number.fromDouble(j, allocator, speed) catch return,
2146 .heading = json_number.fromDouble(j, allocator, bearing) catch return,
2147 // A long, not a double: `put(String, long)` boxes a Long, and
2148 // `numberToString` prints one with `Long.toString` — which needs no
2149 // JNI call, because there is no float format to reproduce.
2150 .timestamp = std.fmt.bufPrint(&timestamp, "{d}", .{time}) catch return,
2151 };
2152
2153 const json = position.renderPosition(allocator, printed) catch return;
2154 events.settle(allocator, position.resolve_global, json) catch {};
2155}
2156
2157/// `nativeLocationFailed(message)` — the failure listener.
2158fn nativeLocationFailed(
2159 env: jni.JNIEnv,
2160 _: jni.jobject,
2161 message: jni.jstring,
2162) callconv(.c) void {
2163 const j = Jni.init(env);
2164 var arena = std.heap.ArenaAllocator.init(backing);
2165 defer arena.deinit();
2166 const allocator = arena.allocator();
2167
2168 // `${jsQuote(e.message)}` — a null message reaches the page as the four
2169 // characters "null", which is what `Any?.toString()` produced there.
2170 const text: []const u8 = if (message == null)
2171 "null"
2172 else
2173 j.stringToUtf8(allocator, message) catch return;
2174
2175 const json = position.renderRejection(allocator, position.code_client_failure, text) catch return;
2176 events.settle(allocator, position.reject_global, json) catch {};
2177}
2178
2179/// `nativeGetCurrentPosition(activity)`.
2180fn nativeGetCurrentPosition(
2181 env: jni.JNIEnv,
2182 _: jni.jobject,
2183 activity: jni.jobject,
2184) callconv(.c) jni.jboolean {
2185 const j = Jni.init(env);
2186 var arena = std.heap.ArenaAllocator.init(backing);
2187 defer arena.deinit();
2188 const allocator = arena.allocator();
2189
2190 const granted = permissions.isGranted(j, activity, permissions.access_fine_location) catch |err| {
2191 std.log.warn("craft: getCurrentPosition fell through to the shim ({s})", .{@errorName(err)});
2192 return jni.JNI_FALSE;
2193 };
2194
2195 if (!granted) {
2196 // The shim asks for both the fine and the coarse permission, so the
2197 // holder does too — a request for one where the shim asked for two
2198 // would leave a page permanently unable to get a coarse fix.
2199 callHolderVoid(j, "requestLocationPermissions", activity) catch |err| {
2200 std.log.warn("craft: getCurrentPosition fell through to the shim ({s})", .{@errorName(err)});
2201 return jni.JNI_FALSE;
2202 };
2203
2204 const json = position.renderRejection(
2205 allocator,
2206 position.code_permission_denied,
2207 position.permission_denied_message,
2208 ) catch return jni.JNI_FALSE;
2209 events.settle(allocator, position.reject_global, json) catch return jni.JNI_FALSE;
2210 return jni.JNI_TRUE;
2211 }
2212
2213 callHolderVoid(j, "requestCurrentPosition", activity) catch |err| {
2214 std.log.warn("craft: getCurrentPosition fell through to the shim ({s})", .{@errorName(err)});
2215 return jni.JNI_FALSE;
2216 };
2217 return jni.JNI_TRUE;
2218}
2219
20992220// =============================================================================
21002221// Tests
21012222// =============================================================================
@@ -2207,7 +2328,7 @@ test "the registered natives name methods the Kotlin actually declares" {
22072328 // A descriptor is checked by the JVM at registration, so a wrong one fails
22082329 // at load rather than at call — but only if the *name* matches something.
22092330 // These two strings are the contract with CraftBridge.kt.
2210 try testing.expectEqual(@as(usize, 56), natives.len);
2331 try testing.expectEqual(@as(usize, 59), natives.len);
22112332 try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name));
22122333
22132334 // And the class they bind to is the fixed one, not the templated bridge.