1use jni::objects::JObject;
2use jni_min_helper::*;
3
4use crate::Error;
5use futures_lite::StreamExt;
6use std::{io::ErrorKind, pin::Pin, task, time::Duration};
7
8use crate::usb::{jerr, list_devices, DeviceInfo};
9
10const USB_SERVICE: &str = "usb";
11const ACTION_USB_DEVICE_ATTACHED: &str = "android.hardware.usb.action.USB_DEVICE_ATTACHED";
12const ACTION_USB_DEVICE_DETACHED: &str = "android.hardware.usb.action.USB_DEVICE_DETACHED";
13const EXTRA_DEVICE: &str = "device";
14const ACTION_USB_PERMISSION: &str = "rust.android_usbser.USB_PERMISSION"; const EXTRA_PERMISSION_GRANTED: &str = "permission";
16
17#[inline(always)]
19pub(crate) fn usb_manager() -> Result<&'static jni::objects::JObject<'static>, Error> {
20 use std::sync::OnceLock;
21 static USB_MAN: OnceLock<jni::objects::GlobalRef> = OnceLock::new();
22 if let Some(ref_man) = USB_MAN.get() {
23 Ok(ref_man.as_obj())
24 } else {
25 let usb_man = get_usb_manager()?;
26 let _ = USB_MAN.set(usb_man.clone());
27 Ok(USB_MAN.get().unwrap().as_obj())
28 }
29}
30
31fn get_usb_manager() -> Result<jni::objects::GlobalRef, Error> {
32 jni_with_env(|env| {
33 let context = android_context();
34 let usb_service_id = USB_SERVICE.new_jobject(env)?;
35 let usb_man = env
36 .call_method(
37 context,
38 "getSystemService",
39 "(Ljava/lang/String;)Ljava/lang/Object;",
40 &[(&usb_service_id).into()],
41 )
42 .get_object(env)?;
43
44 let result = if !usb_man.is_null() {
45 Ok(env.new_global_ref(&usb_man)?)
46 } else {
47 Err(Error::new(
48 ErrorKind::Unsupported,
49 "USB system service not found",
50 ))
51 };
52 Ok(result)
53 })
54 .map_err(jerr)?
55}
56
57pub fn check_attached_intent() -> Result<DeviceInfo, Error> {
61 let dev_info = jni_with_env(|env| {
63 let activity = android_context();
64
65 let intent_startup = env
67 .call_method(activity, "getIntent", "()Landroid/content/Intent;", &[])
68 .get_object(env)?;
69 let action_startup = BroadcastReceiver::get_intent_action(&intent_startup, env)?;
71 if action_startup.trim() != ACTION_USB_DEVICE_ATTACHED {
72 return Ok(Err(Error::from(ErrorKind::NotFound)));
73 }
74 Ok(get_extra_device(&intent_startup, env))
75 })
76 .map_err(jerr)??;
77 if dev_info.check_connection() && dev_info.has_permission()? {
78 Ok(dev_info)
79 } else {
80 Err(Error::from(ErrorKind::NotConnected))
81 }
82}
83
84fn get_extra_device(intent: &JObject<'_>, env: &mut jni::JNIEnv<'_>) -> Result<DeviceInfo, Error> {
85 let extra_device = EXTRA_DEVICE.new_jobject(env).map_err(jerr)?;
86 let java_dev = env
87 .call_method(
88 intent,
89 "getParcelableExtra",
90 "(Ljava/lang/String;)Landroid/os/Parcelable;",
92 &[(&extra_device).into()],
93 )
94 .get_object(env)
95 .map_err(jerr)?;
96
97 if !java_dev.is_null() {
98 DeviceInfo::build(env, &java_dev).map_err(jerr)
99 } else {
100 Err(Error::new(
101 ErrorKind::NotFound,
102 "Unexpected: the Intent has no EXTRA_DEVICE",
103 ))
104 }
105}
106
107pub fn watch_devices() -> Result<HotplugWatch, Error> {
109 BroadcastWaiter::build([ACTION_USB_DEVICE_ATTACHED, ACTION_USB_DEVICE_DETACHED])
110 .map(|waiter| HotplugWatch { waiter })
111 .map_err(jerr)
112}
113
114#[derive(Debug)]
116pub struct HotplugWatch {
117 waiter: BroadcastWaiter,
118}
119
120#[derive(Clone, Debug)]
122pub enum HotplugEvent {
123 Connected(DeviceInfo),
124 Disconnected(DeviceInfo),
125}
126
127#[derive(Debug)]
128struct HotplugWatchFuture<'a> {
129 watch: &'a mut HotplugWatch,
130}
131
132impl HotplugWatch {
133 pub fn count_available(&self) -> usize {
135 self.waiter.count_received()
136 }
137
138 pub fn take_next(&mut self) -> Option<HotplugEvent> {
141 (self.count_available() > 0).then_some(())?;
142 self.wait_blocking(Duration::from_millis(1))
143 }
144
145 pub fn wait_blocking(&mut self, timeout: Duration) -> Option<HotplugEvent> {
148 let fut = HotplugWatchFuture { watch: self };
149 block_for_timeout(fut, timeout)
150 }
151}
152
153impl futures_core::Stream for HotplugWatch {
154 type Item = HotplugEvent;
155
156 fn poll_next(
157 mut self: Pin<&mut Self>,
158 cx: &mut task::Context<'_>,
159 ) -> task::Poll<Option<Self::Item>> {
160 if let task::Poll::Ready(Some(intent)) = self.waiter.poll_next(cx) {
162 let result = jni_with_env(|env| {
163 let action = BroadcastWaiter::get_intent_action(&intent, env)?;
164 Ok(match action.trim() {
165 ACTION_USB_DEVICE_ATTACHED => get_extra_device(intent.as_obj(), env)
166 .ok()
167 .map(HotplugEvent::Connected),
168 ACTION_USB_DEVICE_DETACHED => get_extra_device(intent.as_obj(), env)
169 .ok()
170 .map(HotplugEvent::Disconnected),
171 _ => None,
172 })
173 });
174 if let Ok(Some(event)) = result {
175 task::Poll::Ready(Some(event))
176 } else {
177 task::Poll::Pending
178 }
179 } else {
180 task::Poll::Pending
181 }
182 }
183
184 fn size_hint(&self) -> (usize, Option<usize>) {
185 self.waiter.size_hint()
186 }
187}
188
189impl<'a> std::future::Future for HotplugWatchFuture<'a> {
190 type Output = HotplugEvent;
191 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
192 if let task::Poll::Ready(Some(event)) = self.watch.poll_next(cx) {
193 task::Poll::Ready(event)
194 } else {
195 task::Poll::Pending
196 }
197 }
198}
199
200impl DeviceInfo {
201 pub fn has_permission(&self) -> Result<bool, Error> {
203 let usb_man = usb_manager()?;
204 jni_with_env(|env| {
205 env.call_method(
206 usb_man,
207 "hasPermission",
208 "(Landroid/hardware/usb/UsbDevice;)Z",
209 &[self.internal.as_obj().into()],
210 )
211 .get_boolean()
212 })
213 .map_err(jerr)
214 }
215
216 #[inline(always)]
219 pub fn check_connection(&self) -> bool {
220 let vec_dev = list_devices().unwrap_or_default(); vec_dev.into_iter().any(|ref d| d == self)
222 }
223
224 pub fn request_permission(&self) -> Result<Option<PermissionRequest>, Error> {
233 if !self.check_connection() {
234 return Err(Error::from(ErrorKind::NotConnected));
235 }
236 if self.has_permission()? {
237 return Ok(None);
238 }
239
240 let usb_man = usb_manager()?;
241 jni_with_env(|env| {
242 let context = android_context();
243
244 let str_perm = ACTION_USB_PERMISSION.new_jobject(env)?;
245 let intent = env
246 .new_object(
247 "android/content/Intent",
248 "(Ljava/lang/String;)V",
249 &[(&str_perm).into()],
250 )
251 .auto_local(env)?;
252
253 let flags = if android_api_level() < 31 {
254 0 } else {
256 0x02000000 };
258 let pending = env
259 .call_static_method(
260 "android/app/PendingIntent",
261 "getBroadcast",
262 "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;",
263 &[context.into(), 0_i32.into(), (&intent).into(), flags.into()],
264 )
265 .get_object(env)?;
266
267 env.call_method(
268 usb_man,
269 "requestPermission",
270 "(Landroid/hardware/usb/UsbDevice;Landroid/app/PendingIntent;)V",
271 &[(&self.internal.as_obj()).into(), (&pending).into()],
272 )
273 .clear_ex()?;
274
275 Ok(())
276 })
277 .map_err(jerr)?;
278
279 if self.has_permission()? {
280 return Ok(None); }
282 BroadcastWaiter::build([ACTION_USB_PERMISSION])
283 .map(|waiter| {
284 Some(PermissionRequest {
285 dev_info: self.clone(),
286 waiter,
287 })
288 })
289 .map_err(jerr)
290 }
291
292 pub fn open_device(&self) -> Result<nusb::Device, Error> {
294 if !self.check_connection() {
295 return Err(Error::new(
296 ErrorKind::NotConnected,
297 "the device has been disconnected",
298 ));
299 }
300 if !self.has_permission()? {
301 return Err(Error::from(ErrorKind::PermissionDenied));
302 }
303
304 jni_with_env(|env| {
305 let usb_man = match usb_manager() {
306 Ok(man) => man,
307 Err(e) => return Ok(Err(e)),
308 };
309 let _guard = env.lock_obj(usb_man).unwrap();
312
313 let conn = env
314 .call_method(
315 usb_man,
316 "openDevice",
317 "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;",
318 &[(&self.internal).into()],
319 )
320 .get_object(env)?;
321 if conn.is_null() {
322 return Ok(Err(Error::new(
323 ErrorKind::NotFound,
324 "`UsbManager.openDevice()` failed`",
325 )));
326 }
327 let raw_fd = env
328 .call_method(&conn, "getFileDescriptor", "()I", &[])
329 .get_int()?;
330
331 use std::os::fd::*;
334 log::debug!("Wrapping fd {raw_fd} as usbfs device");
335 let owned_fd = unsafe { OwnedFd::from_raw_fd(raw_fd as RawFd) };
336 Ok(nusb::Device::from_fd(owned_fd))
337 })
338 .map_err(jerr)?
339 }
340}
341
342#[derive(Debug)]
344pub struct PermissionRequest {
345 dev_info: DeviceInfo,
346 waiter: BroadcastWaiter,
347}
348
349impl PermissionRequest {
350 pub fn device_info(&self) -> &DeviceInfo {
352 &self.dev_info
353 }
354
355 pub fn responsed(&self) -> bool {
357 self.waiter.count_received() > 0
358 }
359
360 pub fn take_response(self) -> Option<bool> {
363 self.responsed().then_some(())?;
364 block_for_timeout(self, Duration::from_millis(10))
365 }
366
367 pub fn wait_blocking(self, timeout: Duration) -> Result<bool, Error> {
370 block_for_timeout(self, timeout).ok_or(Error::from(ErrorKind::TimedOut))
371 }
372}
373
374impl std::future::Future for PermissionRequest {
375 type Output = bool;
376
377 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
378 if let task::Poll::Ready(Some(intent)) = self.waiter.poll_next(cx) {
380 let result = jni_with_env(|env| {
381 let Ok(dev_info) = get_extra_device(intent.as_obj(), env) else {
382 return Ok(None);
383 };
384 if dev_info == self.dev_info {
385 let extra_name = EXTRA_PERMISSION_GRANTED.new_jobject(env)?;
386 let granted = env
387 .call_method(
388 &intent,
389 "getBooleanExtra",
390 "(Ljava/lang/String;Z)Z",
391 &[(&extra_name).into(), false.into()],
392 )
393 .get_boolean()
394 .unwrap_or(false);
395 let _ = self.waiter.receiver().unregister();
396 Ok(Some(granted))
397 } else {
398 Ok(None)
399 }
400 });
401 if let Ok(Some(granted)) = result {
402 task::Poll::Ready(granted)
403 } else if let Ok(None) = result {
404 task::Poll::Pending
405 } else {
406 task::Poll::Ready(false)
407 }
408 } else {
409 task::Poll::Pending
410 }
411 }
412}