ReviewOS

also looking at this

craft-native/craft

feat(android): the shortcut pair in Zig — ratchet 75 → 73

#171
Merged chris wants to merge feat/android-shortcuts into main
6 files +602 -3
packages/zig/src/android_dispatch.zigmodified+95-1
Changes to packages/zig/src/android_dispatch.zig
@@ -51,6 +51,7 @@ const db = ;
5151const shareditem = ;
5252const contacts = ;
5353const widgets = ;
54const shortcuts = ;
5455
5556const Jni = jni.Jni;
5657
@@ -307,6 +308,16 @@ const natives = [_]jni.JNINativeMethod{
307308 .signature = "(Landroid/app/Activity;Ljava/lang/String;)Z",
308309 .fnPtr = @ptrCast(&nativeReloadWidgets),
309310 },
311 .{
312 .name = "nativeSetShortcuts",
313 .signature = "(Landroid/app/Activity;Ljava/lang/String;)Z",
314 .fnPtr = @ptrCast(&nativeSetShortcuts),
315 },
316 .{
317 .name = "nativeClearShortcuts",
318 .signature = "(Landroid/app/Activity;)Z",
319 .fnPtr = @ptrCast(&nativeClearShortcuts),
320 },
310321};
311322
312323/// How binding went. A value rather than a log line, so every path is
@@ -1152,6 +1163,89 @@ fn nativeReloadWidgets(
11521163 return jni.JNI_TRUE;
11531164}
11541165
1166/// `nativeSetShortcuts(activity, shortcutsJson)`.
1167fn nativeSetShortcuts(
1168 env: jni.JNIEnv,
1169 _: jni.jobject,
1170 activity: jni.jobject,
1171 shortcuts_json: jni.jstring,
1172) callconv(.c) jni.jboolean {
1173 const j = Jni.init(env);
1174 var arena = std.heap.ArenaAllocator.init(backing);
1175 defer arena.deinit();
1176 const allocator = arena.allocator();
1177
1178 const sdk = shortcuts.sdkInt(j) catch |err| {
1179 std.log.warn("craft: setShortcuts fell through to the shim ({s})", .{@errorName(err)});
1180 return jni.JNI_FALSE;
1181 };
1182
1183 if (sdk < shortcuts.n_mr1) {
1184 const payload = shortcuts.jsonString(allocator, shortcuts.unsupported_message) catch
1185 return jni.JNI_FALSE;
1186 events.settle(allocator, shortcuts.reject_global, payload) catch return jni.JNI_FALSE;
1187 return jni.JNI_TRUE;
1188 }
1189
1190 const text = j.stringToUtf8(allocator, shortcuts_json) catch return jni.JNI_FALSE;
1191 var parsed = std.json.parseFromSlice(std.json.Value, allocator, text, .{}) catch
1192 return jni.JNI_FALSE;
1193 defer parsed.deinit();
1194
1195 // `getString` raises where `optString` defaults, so a payload missing a
1196 // title is a rejection rather than a shape to hand back — and the shim
1197 // sets nothing at all, including the entries that parsed.
1198 const list = shortcuts.parseShortcuts(allocator, parsed.value) catch |err| {
1199 const payload = shortcuts.jsonString(allocator, @errorName(err)) catch
1200 return jni.JNI_FALSE;
1201 events.settle(allocator, shortcuts.reject_global, payload) catch return jni.JNI_FALSE;
1202 return jni.JNI_TRUE;
1203 } orelse return jni.JNI_FALSE;
1204
1205 shortcuts.set(j, allocator, activity, list) catch |err| {
1206 const payload = shortcuts.jsonString(allocator, @errorName(err)) catch
1207 return jni.JNI_FALSE;
1208 events.settle(allocator, shortcuts.reject_global, payload) catch return jni.JNI_FALSE;
1209 return jni.JNI_TRUE;
1210 };
1211
1212 const payload = shortcuts.countPayload(allocator, list.len) catch return jni.JNI_FALSE;
1213 events.settle(allocator, shortcuts.resolve_global, payload) catch return jni.JNI_FALSE;
1214 return jni.JNI_TRUE;
1215}
1216
1217/// `nativeClearShortcuts(activity)`.
1218///
1219/// Resolves `true` below API 25 without touching anything, because that is
1220/// what the shim does: nothing to clear is the same as having cleared it.
1221fn nativeClearShortcuts(
1222 env: jni.JNIEnv,
1223 _: jni.jobject,
1224 activity: jni.jobject,
1225) callconv(.c) jni.jboolean {
1226 const j = Jni.init(env);
1227 var arena = std.heap.ArenaAllocator.init(backing);
1228 defer arena.deinit();
1229 const allocator = arena.allocator();
1230
1231 const sdk = shortcuts.sdkInt(j) catch |err| {
1232 std.log.warn("craft: clearShortcuts fell through to the shim ({s})", .{@errorName(err)});
1233 return jni.JNI_FALSE;
1234 };
1235
1236 if (sdk >= shortcuts.n_mr1) {
1237 shortcuts.clear(j, activity) catch |err| {
1238 const payload = shortcuts.jsonString(allocator, @errorName(err)) catch
1239 return jni.JNI_FALSE;
1240 events.settle(allocator, shortcuts.reject_global, payload) catch return jni.JNI_FALSE;
1241 return jni.JNI_TRUE;
1242 };
1243 }
1244
1245 events.settle(allocator, shortcuts.resolve_global, "true") catch return jni.JNI_FALSE;
1246 return jni.JNI_TRUE;
1247}
1248
11551249// =============================================================================
11561250// Tests
11571251// =============================================================================
@@ -1263,7 +1357,7 @@ test "the registered natives name methods the Kotlin actually declares" {
12631357 // A descriptor is checked by the JVM at registration, so a wrong one fails
12641358 // at load rather than at call — but only if the *name* matches something.
12651359 // These two strings are the contract with CraftBridge.kt.
1266 try testing.expectEqual(@as(usize, 28), natives.len);
1360 try testing.expectEqual(@as(usize, 30), natives.len);
12671361 try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name));
12681362
12691363 // And the class they bind to is the fixed one, not the templated bridge.