Krolyxon
Commit 05f4013

Branches

Refs →

budctlcratesbudctl-androidsrckotlin.rs

151 lines · 5.0 KB · Rust

1//! [`Phone`] over JNI: calls into the app's `Platform` object (`Platform.kt`) from the worker's
2//! thread. The thread attaches to the VM on its first call and stays attached until it ends;
3//! each call gets a JNI frame of its own, so no local reference outlives it.
4
5use std::time::Duration;
6
7use budctl_core::{Error, Result};
8use jni::objects::{JByteArray, JObject, JString};
9use jni::refs::Global;
10use jni::strings::JNIStr;
11use jni::vm::JavaVM;
12use jni::{Env, JValue, jni_sig, jni_str};
13
14use crate::json;
15use crate::phone::{Phone, app_error};
16
17pub struct Kotlin {
18 platform: Global<JObject<'static>>,
19}
20
21impl Kotlin {
22 pub fn new(platform: Global<JObject<'static>>) -> Self {
23 Kotlin { platform }
24 }
25
26 /// Runs `call` on this thread's JNI environment. A Java exception or a JNI failure becomes an
27 /// app error about `what`.
28 fn call<T>(
29 &self,
30 what: &'static str,
31 call: impl FnOnce(&mut Env, &JObject<'static>) -> jni::errors::Result<T>,
32 ) -> Result<T> {
33 let vm = JavaVM::singleton().map_err(|e| app_error(what, e))?;
34 vm.attach_current_thread(|env| call(env, &self.platform))
35 .map_err(|e| app_error(what, e))
36 }
37
38 /// Calls a `(String) -> Unit` method, for reports: a failure has nowhere to go.
39 fn tell(&self, method: &'static JNIStr, text: &str) {
40 let _ = self.call("reporting to the app", |env, platform| {
41 let text = env.new_string(text)?;
42 env.call_method(
43 platform,
44 method,
45 jni_sig!((text: java.lang.String) -> void),
46 &[JValue::Object(&text)],
47 )?
48 .v()
49 });
50 }
51}
52
53/// A Kotlin `String?` as a Rust string.
54fn string(env: &mut Env, object: JObject) -> jni::errors::Result<Option<String>> {
55 if object.is_null() {
56 return Ok(None);
57 }
58 let string = env.cast_local::<JString>(object)?;
59 Ok(Some(string.try_to_string(env)?))
60}
61
62impl Phone for Kotlin {
63 fn paired_devices(&self) -> Result<String> {
64 let list = self.call("listing the paired devices", |env, platform| {
65 let list = env
66 .call_method(
67 platform,
68 jni_str!("pairedDevices"),
69 jni_sig!(() -> java.lang.String),
70 &[],
71 )?
72 .l()?;
73 string(env, list)
74 })?;
75 Ok(list.unwrap_or_default())
76 }
77
78 fn connect(&self, address: &str, service: &str) -> Result<()> {
79 let failure = self.call("connecting to the control channel", |env, platform| {
80 let address = env.new_string(address)?;
81 let service = env.new_string(service)?;
82 let failure = env
83 .call_method(
84 platform,
85 jni_str!("connect"),
86 jni_sig!((address: java.lang.String, service: java.lang.String) -> java.lang.String),
87 &[JValue::Object(&address), JValue::Object(&service)],
88 )?
89 .l()?;
90 string(env, failure)
91 })?;
92 match failure {
93 None => Ok(()),
94 Some(json) => Err(json::connect_failure(&json)),
95 }
96 }
97
98 fn send(&self, bytes: &[u8]) -> Result<()> {
99 let failure = self.call("sending to the buds", |env, platform| {
100 let bytes = env.byte_array_from_slice(bytes)?;
101 let failure = env
102 .call_method(
103 platform,
104 jni_str!("send"),
105 jni_sig!((bytes: jbyte[]) -> java.lang.String),
106 &[JValue::Object(&bytes)],
107 )?
108 .l()?;
109 string(env, failure)
110 })?;
111 match failure {
112 None => Ok(()),
113 Some(message) => Err(Error::NotConnected(format!("sending to the buds: {message}"))),
114 }
115 }
116
117 fn recv(&self, timeout: Duration) -> Result<Option<Vec<u8>>> {
118 // Rounded up, so a wait shorter than a millisecond still waits.
119 let millis = i64::try_from(timeout.as_micros().div_ceil(1000)).unwrap_or(i64::MAX);
120 self.call("reading from the buds", |env, platform| {
121 let bytes = env
122 .call_method(
123 platform,
124 jni_str!("recv"),
125 jni_sig!((timeout_ms: jlong) -> jbyte[]),
126 &[JValue::Long(millis)],
127 )?
128 .l()?;
129 if bytes.is_null() {
130 return Ok(None);
131 }
132 let bytes = env.cast_local::<JByteArray>(bytes)?;
133 Ok(Some(env.convert_byte_array(&bytes)?))
134 })
135 }
136
137 fn close(&self) {
138 let _ = self.call("closing the link", |env, platform| {
139 env.call_method(platform, jni_str!("close"), jni_sig!(() -> void), &[])?
140 .v()
141 });
142 }
143
144 fn event(&self, json: &str) {
145 self.tell(jni_str!("event"), json);
146 }
147
148 fn log(&self, line: &str) {
149 self.tell(jni_str!("log"), line);
150 }
151}