also looking at this
feat(android): getCurrentPosition in Zig — ratchet 52 → 51
#188
6 files
+466
-3
| @@ -45,6 +45,7 @@ const network = ; | ||
| 45 | 45 | const appstate = ; |
| 46 | 46 | const bluetooth = ; |
| 47 | 47 | const motion = ; |
| 48 | const position = ; | |
| 48 | 49 | const json_number = ; |
| 49 | 50 | const securestore = ; |
| 50 | 51 | const haptics = ; |
| @@ -467,6 +468,23 @@ const natives = [_]jni.JNINativeMethod{ | ||
| 467 | 468 | .signature = "(Landroid/app/Activity;)Z", |
| 468 | 469 | .fnPtr = @ptrCast(&nativeStopMotionUpdates), |
| 469 | 470 | }, |
| 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 | }, | |
| 470 | 488 | }; |
| 471 | 489 | |
| 472 | 490 | /// 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 | ||
| 2096 | 2114 | ); |
| 2097 | 2115 | } |
| 2098 | 2116 | |
| 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. | |
| 2123 | fn 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(×tamp, "{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. | |
| 2158 | fn 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)`. | |
| 2180 | fn 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 | ||
| 2099 | 2220 | // ============================================================================= |
| 2100 | 2221 | // Tests |
| 2101 | 2222 | // ============================================================================= |
| @@ -2207,7 +2328,7 @@ test "the registered natives name methods the Kotlin actually declares" { | ||
| 2207 | 2328 | // A descriptor is checked by the JVM at registration, so a wrong one fails |
| 2208 | 2329 | // at load rather than at call — but only if the *name* matches something. |
| 2209 | 2330 | // 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); | |
| 2211 | 2332 | try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name)); |
| 2212 | 2333 | |
| 2213 | 2334 | // And the class they bind to is the fixed one, not the templated bridge. |