also looking at this
feat(android): the motion pair, and org.json's numbers — ratchet 54 → 52
#187
7 files
+577
-3
| @@ -44,6 +44,8 @@ const intents = ; | ||
| 44 | 44 | const network = ; |
| 45 | 45 | const appstate = ; |
| 46 | 46 | const bluetooth = ; |
| 47 | const motion = ; | |
| 48 | const json_number = ; | |
| 47 | 49 | const securestore = ; |
| 48 | 50 | const haptics = ; |
| 49 | 51 | const notifications = ; |
| @@ -449,6 +451,22 @@ const natives = [_]jni.JNINativeMethod{ | ||
| 449 | 451 | .signature = "(Landroid/app/Activity;)Z", |
| 450 | 452 | .fnPtr = @ptrCast(&nativeStopBluetoothScan), |
| 451 | 453 | }, |
| 454 | // Not an action: the other end of the SensorEventListener the holder owns. | |
| 455 | .{ | |
| 456 | .name = "nativeMotionSample", | |
| 457 | .signature = "(ZFFF)V", | |
| 458 | .fnPtr = @ptrCast(&nativeMotionSample), | |
| 459 | }, | |
| 460 | .{ | |
| 461 | .name = "nativeStartMotionUpdates", | |
| 462 | .signature = "(Landroid/app/Activity;I)Z", | |
| 463 | .fnPtr = @ptrCast(&nativeStartMotionUpdates), | |
| 464 | }, | |
| 465 | .{ | |
| 466 | .name = "nativeStopMotionUpdates", | |
| 467 | .signature = "(Landroid/app/Activity;)Z", | |
| 468 | .fnPtr = @ptrCast(&nativeStopMotionUpdates), | |
| 469 | }, | |
| 452 | 470 | }; |
| 453 | 471 | |
| 454 | 472 | /// How binding went. A value rather than a log line, so every path is |
| @@ -1975,6 +1993,109 @@ fn nativeStopBluetoothScan( | ||
| 1975 | 1993 | return jni.JNI_TRUE; |
| 1976 | 1994 | } |
| 1977 | 1995 | |
| 1996 | /// `nativeMotionSample(isAccelerometer, x, y, z)` — one sensor reading. | |
| 1997 | /// | |
| 1998 | /// Every sample sends a complete event: the sensor that just reported and the | |
| 1999 | /// last value the other one gave, which is what the shim does and is why a | |
| 2000 | /// device with both sensors produces two events per cycle. | |
| 2001 | fn nativeMotionSample( | |
| 2002 | env: jni.JNIEnv, | |
| 2003 | _: jni.jobject, | |
| 2004 | is_accelerometer: jni.jboolean, | |
| 2005 | x: jni.jfloat, | |
| 2006 | y: jni.jfloat, | |
| 2007 | z: jni.jfloat, | |
| 2008 | ) callconv(.c) void { | |
| 2009 | const j = Jni.init(env); | |
| 2010 | var arena = std.heap.ArenaAllocator.init(backing); | |
| 2011 | defer arena.deinit(); | |
| 2012 | const allocator = arena.allocator(); | |
| 2013 | ||
| 2014 | motion.record(is_accelerometer != jni.JNI_FALSE, .{ x, y, z }); | |
| 2015 | ||
| 2016 | const acceleration = motion.lastAcceleration(); | |
| 2017 | const rotation = motion.lastRotation(); | |
| 2018 | ||
| 2019 | // Formatted by org.json, because these are the numbers a page reads and | |
| 2020 | // `Float.toString` is not a format string — see `android_json_number`. | |
| 2021 | var printed: [6][]const u8 = undefined; | |
| 2022 | inline for (0..3) |i| { | |
| 2023 | printed[i] = json_number.fromFloat(j, allocator, acceleration[i]) catch return; | |
| 2024 | printed[i + 3] = json_number.fromFloat(j, allocator, rotation[i]) catch return; | |
| 2025 | } | |
| 2026 | ||
| 2027 | motion.announce( | |
| 2028 | allocator, | |
| 2029 | .{ printed[0], printed[1], printed[2] }, | |
| 2030 | .{ printed[3], printed[4], printed[5] }, | |
| 2031 | ) catch {}; | |
| 2032 | } | |
| 2033 | ||
| 2034 | /// `nativeStartMotionUpdates(activity, intervalMs)`. | |
| 2035 | /// | |
| 2036 | /// The delay constant is read here and handed across, so the shim's `when` | |
| 2037 | /// stays a Zig decision while the field it names stays the platform's number. | |
| 2038 | fn nativeStartMotionUpdates( | |
| 2039 | env: jni.JNIEnv, | |
| 2040 | _: jni.jobject, | |
| 2041 | activity: jni.jobject, | |
| 2042 | interval_ms: jni.jint, | |
| 2043 | ) callconv(.c) jni.jboolean { | |
| 2044 | const j = Jni.init(env); | |
| 2045 | ||
| 2046 | const delay = sensorDelay(j, motion.delayFor(interval_ms)) catch |err| { | |
| 2047 | std.log.warn("craft: startMotionUpdates fell through to the shim ({s})", .{@errorName(err)}); | |
| 2048 | return jni.JNI_FALSE; | |
| 2049 | }; | |
| 2050 | ||
| 2051 | // The shim builds a new listener object, whose lastAccel and lastGyro | |
| 2052 | // start null — so a restart forgets what the last one saw. | |
| 2053 | motion.reset(); | |
| 2054 | ||
| 2055 | callHolderInt(j, "startMotionWatch", activity, delay) catch |err| { | |
| 2056 | std.log.warn("craft: startMotionUpdates fell through to the shim ({s})", .{@errorName(err)}); | |
| 2057 | return jni.JNI_FALSE; | |
| 2058 | }; | |
| 2059 | return jni.JNI_TRUE; | |
| 2060 | } | |
| 2061 | ||
| 2062 | /// `nativeStopMotionUpdates(activity)`. | |
| 2063 | fn nativeStopMotionUpdates( | |
| 2064 | env: jni.JNIEnv, | |
| 2065 | _: jni.jobject, | |
| 2066 | activity: jni.jobject, | |
| 2067 | ) callconv(.c) jni.jboolean { | |
| 2068 | const j = Jni.init(env); | |
| 2069 | ||
| 2070 | callHolderVoid(j, "stopMotionWatch", activity) catch |err| { | |
| 2071 | std.log.warn("craft: stopMotionUpdates failed ({s})", .{@errorName(err)}); | |
| 2072 | return jni.JNI_FALSE; | |
| 2073 | }; | |
| 2074 | return jni.JNI_TRUE; | |
| 2075 | } | |
| 2076 | ||
| 2077 | /// `SensorManager.<field>`. | |
| 2078 | fn sensorDelay(j: Jni, delay: motion.Delay) !jni.jint { | |
| 2079 | try j.pushLocalFrame(4); | |
| 2080 | defer _ = j.popLocalFrame(null); | |
| 2081 | ||
| 2082 | const manager_cls = try j.findClass("android/hardware/SensorManager"); | |
| 2083 | return j.staticIntField(manager_cls, try j.staticFieldId(manager_cls, delay.field(), "I")); | |
| 2084 | } | |
| 2085 | ||
| 2086 | /// Call an `(Activity, int) -> void` static on the holder. | |
| 2087 | fn callHolderInt(j: Jni, comptime name: [:0]const u8, activity: jni.jobject, value: jni.jint) !void { | |
| 2088 | try j.pushLocalFrame(8); | |
| 2089 | defer _ = j.popLocalFrame(null); | |
| 2090 | ||
| 2091 | const holder = try j.findClass(holder_class); | |
| 2092 | try j.callStaticVoidMethodA( | |
| 2093 | holder, | |
| 2094 | try j.staticMethodId(holder, name, "(Landroid/app/Activity;I)V"), | |
| 2095 | &.{ .{ .l = activity }, .{ .i = value } }, | |
| 2096 | ); | |
| 2097 | } | |
| 2098 | ||
| 1978 | 2099 | // ============================================================================= |
| 1979 | 2100 | // Tests |
| 1980 | 2101 | // ============================================================================= |
| @@ -2086,7 +2207,7 @@ test "the registered natives name methods the Kotlin actually declares" { | ||
| 2086 | 2207 | // A descriptor is checked by the JVM at registration, so a wrong one fails |
| 2087 | 2208 | // at load rather than at call — but only if the *name* matches something. |
| 2088 | 2209 | // These two strings are the contract with CraftBridge.kt. |
| 2089 | try testing.expectEqual(@as(usize, 53), natives.len); | |
| 2210 | try testing.expectEqual(@as(usize, 56), natives.len); | |
| 2090 | 2211 | try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name)); |
| 2091 | 2212 | |
| 2092 | 2213 | // And the class they bind to is the fixed one, not the templated bridge. |