also looking at this
feat(android): getCalendarEvents and createCalendarEvent in Zig — ratchet 86 → 84
#158
7 files
+1167
-20
| @@ -0,0 +1,98 @@ | ||
| 1 | //! Runtime permissions, the two calls every guarded action starts with. | |
| 2 | //! | |
| 3 | //! The Kotlin reaches these through AndroidX: | |
| 4 | //! | |
| 5 | //! ```kotlin | |
| 6 | //! if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CALENDAR) | |
| 7 | //! != PackageManager.PERMISSION_GRANTED) { | |
| 8 | //! ActivityCompat.requestPermissions(activity, arrayOf(...), REQUEST_CALENDAR) | |
| 9 | //! ``` | |
| 10 | //! | |
| 11 | //! Zig calls the framework methods those two delegate to. That is not a | |
| 12 | //! shortcut around a dependency — `ContextCompat.checkSelfPermission` *is* | |
| 13 | //! `Context.checkSelfPermission` from API 23, and `ActivityCompat`'s | |
| 14 | //! `requestPermissions` is `Activity.requestPermissions` from the same level, | |
| 15 | //! with the pre-23 branches dead on any modern `minSdk`. Going through the | |
| 16 | //! framework also means no `androidx.core` on the JNI side, which matters | |
| 17 | //! because `libcraft.so` is built with no NDK and links nothing. | |
| 18 | //! | |
| 19 | //! ## Which thread | |
| 20 | //! | |
| 21 | //! `requestPermissions` is documented as main-thread, and this is called from | |
| 22 | //! the WebView's JavaBridge thread — because the shim calls it from there too. | |
| 23 | //! Hoisting it onto the main looper here would be a divergence, and a silent | |
| 24 | //! one: the two would then differ in when the dialog appears relative to the | |
| 25 | //! rejection the page already received. | |
| 26 | ||
| 27 | const std = ; | |
| 28 | const jni = ; | |
| 29 | ||
| 30 | const Jni = jni.Jni; | |
| 31 | const jobject = jni.jobject; | |
| 32 | ||
| 33 | /// `PackageManager.PERMISSION_GRANTED`. | |
| 34 | /// | |
| 35 | /// A compile-time constant in Java, so the shim's DEX holds the literal `0` | |
| 36 | /// rather than a field read — which is why it is a literal here as well. | |
| 37 | pub const granted: i32 = 0; | |
| 38 | ||
| 39 | pub const read_calendar = "android.permission.READ_CALENDAR"; | |
| 40 | pub const write_calendar = "android.permission.WRITE_CALENDAR"; | |
| 41 | ||
| 42 | /// `activity.checkSelfPermission(permission) == PERMISSION_GRANTED`. | |
| 43 | pub fn isGranted(j: Jni, activity: jobject, permission: [*:0]const u8) !bool { | |
| 44 | try j.pushLocalFrame(8); | |
| 45 | defer _ = j.popLocalFrame(null); | |
| 46 | ||
| 47 | const name = try j.newStringUtf(permission); | |
| 48 | const activity_cls = try j.objectClass(activity); | |
| 49 | const result = try j.callIntMethodA( | |
| 50 | activity, | |
| 51 | try j.methodId(activity_cls, "checkSelfPermission", "(Ljava/lang/String;)I"), | |
| 52 | &.{.{ .l = name }}, | |
| 53 | ); | |
| 54 | return result == granted; | |
| 55 | } | |
| 56 | ||
| 57 | /// `activity.requestPermissions(new String[]{permission}, code)`. | |
| 58 | /// | |
| 59 | /// The result arrives at `onRequestPermissionsResult`, which the shim does not | |
| 60 | /// implement for these codes — so the request is what makes the *next* call | |
| 61 | /// succeed, and this one still fails. That is the shim's behaviour and the | |
| 62 | /// reason the caller rejects immediately after. | |
| 63 | pub fn request(j: Jni, activity: jobject, permission: [*:0]const u8, code: i32) !void { | |
| 64 | try j.pushLocalFrame(8); | |
| 65 | defer _ = j.popLocalFrame(null); | |
| 66 | ||
| 67 | const string_cls = try j.findClass("java/lang/String"); | |
| 68 | const names = try j.newObjectArray(1, string_cls); | |
| 69 | try j.setObjectArrayElement(names, 0, try j.newStringUtf(permission)); | |
| 70 | ||
| 71 | const activity_cls = try j.objectClass(activity); | |
| 72 | try j.callVoidMethodA( | |
| 73 | activity, | |
| 74 | try j.methodId(activity_cls, "requestPermissions", "([Ljava/lang/String;I)V"), | |
| 75 | &.{ .{ .l = names }, .{ .i = code } }, | |
| 76 | ); | |
| 77 | } | |
| 78 | ||
| 79 | // ============================================================================= | |
| 80 | // Tests | |
| 81 | // ============================================================================= | |
| 82 | ||
| 83 | const testing = std.testing; | |
| 84 | ||
| 85 | test "the permission names match Manifest.permission exactly" { | |
| 86 | // `Manifest.permission.READ_CALENDAR` is a compile-time constant, so the | |
| 87 | // shim compares against these exact strings. A typo here is a permission | |
| 88 | // that is never granted and an action that always rejects. | |
| 89 | try testing.expectEqualStrings("android.permission.READ_CALENDAR", read_calendar); | |
| 90 | try testing.expectEqualStrings("android.permission.WRITE_CALENDAR", write_calendar); | |
| 91 | } | |
| 92 | ||
| 93 | test "PERMISSION_GRANTED is zero, and PERMISSION_DENIED is not" { | |
| 94 | // -1 is PERMISSION_DENIED. The comparison is `== granted` rather than | |
| 95 | // `!= denied` because a future third value would then read as granted. | |
| 96 | try testing.expectEqual(@as(i32, 0), granted); | |
| 97 | try testing.expect(granted != -1); | |
| 98 | } | |