1use tauri::{plugin::TauriPlugin, Runtime};
2
3#[cfg(target_os = "android")]
4use std::sync::atomic::{AtomicBool, Ordering};
5#[cfg(target_os = "android")]
6use std::sync::OnceLock;
7
8#[cfg(target_os = "android")]
9use tokio::sync::broadcast;
10
11#[cfg(target_os = "android")]
12const PLUGIN_IDENTIFIER: &str = "org.jakebot.blew";
13
14#[cfg(target_os = "android")]
15static AUTO_REQUEST_PERMISSIONS: AtomicBool = AtomicBool::new(true);
16
17#[cfg(target_os = "android")]
18static PERMISSIONS_TX: OnceLock<broadcast::Sender<BlePermissionStatus>> = OnceLock::new();
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum BlePermissionStatus {
23 Granted,
26 Denied,
28}
29
30impl BlePermissionStatus {
31 #[must_use]
32 pub fn is_granted(self) -> bool {
33 matches!(self, BlePermissionStatus::Granted)
34 }
35}
36
37#[derive(Clone, Debug)]
39pub struct BlewPluginConfig {
40 pub auto_request_permissions: bool,
45}
46
47impl Default for BlewPluginConfig {
48 fn default() -> Self {
49 Self {
50 auto_request_permissions: true,
51 }
52 }
53}
54
55pub fn are_ble_permissions_granted() -> bool {
59 #[cfg(target_os = "android")]
60 {
61 blew::platform::android::are_ble_permissions_granted()
62 }
63 #[cfg(not(target_os = "android"))]
64 {
65 true
66 }
67}
68
69pub fn request_ble_permissions() {
79 #[cfg(target_os = "android")]
80 {
81 blew::platform::android::request_ble_permissions();
82 }
83}
84
85#[cfg(target_os = "android")]
97pub fn permission_events() -> blew::util::BroadcastEventStream<BlePermissionStatus> {
98 let tx = PERMISSIONS_TX.get_or_init(|| broadcast::channel(16).0);
99 blew::util::BroadcastEventStream::new(tx.subscribe())
100}
101
102pub fn is_emulator() -> bool {
107 #[cfg(target_os = "android")]
108 {
109 blew::platform::android::is_emulator()
110 }
111 #[cfg(not(target_os = "android"))]
112 {
113 std::env::var("SIMULATOR_DEVICE_NAME").is_ok()
114 }
115}
116
117pub fn init<R: Runtime>() -> TauriPlugin<R> {
120 init_with_config(BlewPluginConfig::default())
121}
122
123pub fn init_with_config<R: Runtime>(config: BlewPluginConfig) -> TauriPlugin<R> {
125 #[cfg(target_os = "android")]
126 {
127 AUTO_REQUEST_PERMISSIONS.store(config.auto_request_permissions, Ordering::Relaxed);
128 let _ = PERMISSIONS_TX.get_or_init(|| broadcast::channel(16).0);
129 }
130 #[cfg(not(target_os = "android"))]
131 {
132 let _ = config;
133 }
134
135 tauri::plugin::Builder::<R>::new("blew")
136 .setup(|_app, api| {
137 #[cfg(target_os = "android")]
138 {
139 let vm_ptr = install_android_context()?;
140 let vm = unsafe { jni::JavaVM::from_raw(vm_ptr.cast()) };
141 blew::platform::android::init_jvm(vm);
142 api.register_android_plugin(PLUGIN_IDENTIFIER, "BlewPlugin")?;
143 }
144 let _ = api;
145 Ok(())
146 })
147 .build()
148}
149
150#[cfg(target_os = "android")]
154fn install_android_context() -> Result<*mut std::ffi::c_void, Box<dyn std::error::Error>> {
155 use std::ffi::c_void;
156 use std::sync::mpsc;
157 use tauri::wry::prelude::{dispatch, jni as wry_jni};
158
159 let (tx, rx) = mpsc::channel();
160 dispatch(move |env, activity, _webview| {
161 let result: Result<_, wry_jni::errors::Error> = (|| {
162 let vm = env.get_java_vm()?;
163 let activity_global = env.new_global_ref(activity)?;
164 Ok((vm, activity_global))
165 })();
166 let _ = tx.send(result);
167 });
168 let (vm, activity_global) = rx
169 .recv()
170 .map_err(|e| format!("wry JNI dispatch never returned: {e}"))?
171 .map_err(|e| format!("JNI error capturing JVM/activity: {e}"))?;
172
173 let vm_ptr = vm.get_java_vm_pointer() as *mut c_void;
174 let activity_ptr = activity_global.as_obj().as_raw() as *mut c_void;
175 unsafe {
176 ndk_context::initialize_android_context(vm_ptr, activity_ptr);
177 }
178 std::mem::forget(activity_global);
181 Ok(vm_ptr)
182}
183
184#[cfg(target_os = "android")]
192#[unsafe(no_mangle)]
193pub unsafe extern "C" fn Java_org_jakebot_blew_BlewPluginNative_autoRequestPermissionsEnabled(
194 _env: jni::EnvUnowned,
195 _class: jni::objects::JClass,
196) -> jni::sys::jboolean {
197 AUTO_REQUEST_PERMISSIONS.load(Ordering::Relaxed)
198}
199
200#[cfg(target_os = "android")]
209#[unsafe(no_mangle)]
210pub unsafe extern "C" fn Java_org_jakebot_blew_BlewPluginNative_onPermissionsChanged(
211 _env: jni::EnvUnowned,
212 _class: jni::objects::JClass,
213 granted: jni::sys::jboolean,
214) {
215 let status = if granted {
216 BlePermissionStatus::Granted
217 } else {
218 BlePermissionStatus::Denied
219 };
220 if let Some(tx) = PERMISSIONS_TX.get() {
221 let _ = tx.send(status);
222 }
223}