1use std::collections::HashMap;
4use std::sync::{Arc, OnceLock};
5
6use futures_core::Stream;
7use futures_lite::{stream, StreamExt};
8use java_spaghetti::{ByteArray, Env, Global, Local, Null, Ref};
9use log::{debug, warn};
10use uuid::Uuid;
11
12use super::async_util::StreamUntil;
13use super::bindings::android::bluetooth::le::{
14 ScanCallback, ScanFilter_Builder, ScanResult, ScanSettings, ScanSettings_Builder,
15};
16use super::bindings::android::bluetooth::{
17 BluetoothAdapter, BluetoothDevice, BluetoothGattCallback, BluetoothManager, BluetoothProfile,
18};
19use super::bindings::android::content::Context as AndroidContext;
20use super::bindings::android::os::ParcelUuid;
21use super::bindings::java::lang::String as JString;
22use super::bindings::java::util::Map_Entry;
23use super::bindings::java::{self};
24use super::device::Device;
25use super::error::ErrorKind;
26use super::event_receiver::{EventReceiver, GlobalEvent};
27use super::gatt_tree::{BluetoothGattCallbackProxy, CachedWeak, GattTree};
28use super::jni::{ByteArrayExt, Monitor, VM};
29use super::vm_context::{
30 android_api_level, android_context, android_has_permission, jni_get_vm, jni_set_vm,
31 jni_with_env,
32};
33use crate::util::{defer, JavaIterator, OptionExt, UuidExt};
34use crate::{
35 AdapterEvent, AdvertisementData, AdvertisingDevice, ConnectionEvent, DeviceId, Error,
36 ManufacturerData, Result,
37};
38
39#[derive(Clone)]
41pub struct Adapter {
42 inner: Arc<AdapterInner>,
43}
44
45struct AdapterInner {
46 #[allow(unused)]
47 manager: Global<BluetoothManager>,
48 adapter: Global<BluetoothAdapter>,
49 global_event_receiver: Arc<EventReceiver>,
50 request_mtu_on_connect: bool,
51 allow_multiple_connections: bool,
52}
53
54static CONN_MUTEX: async_lock::Mutex<()> = async_lock::Mutex::new(());
55
56pub struct AdapterConfig {
63 vm: *mut java_spaghetti::sys::JavaVM,
66 manager: java_spaghetti::sys::jobject,
69
70 request_mtu_on_connect: bool,
71 allow_multiple_connections: bool,
72}
73
74unsafe impl Send for AdapterConfig {}
75
76impl AdapterConfig {
77 pub unsafe fn new(
90 java_vm: *mut java_spaghetti::sys::JavaVM,
91 bluetooth_manager: java_spaghetti::sys::jobject,
92 ) -> Self {
93 Self {
94 vm: java_vm,
95 manager: bluetooth_manager,
96 request_mtu_on_connect: true,
97 allow_multiple_connections: true,
98 }
99 }
100
101 pub fn request_mtu_on_connect(mut self, enabled: bool) -> Self {
108 self.request_mtu_on_connect = enabled;
109 self
110 }
111
112 pub fn allow_multiple_connections(mut self, enabled: bool) -> Self {
119 self.allow_multiple_connections = enabled;
120 self
121 }
122}
123
124impl Default for AdapterConfig {
125 fn default() -> Self {
126 jni_with_env(|env| {
127 let context = android_context().as_local(env);
128 let service_name = JString::from_env_str(env, AndroidContext::BLUETOOTH_SERVICE);
129 let manager = context
130 .getSystemService_String(service_name)
131 .unwrap()
132 .expect("Context.getSystemService() returned null for BLUETOOTH_SERVICE")
133 .cast::<BluetoothManager>()?
134 .as_global();
135 let config = unsafe { Self::new(jni_get_vm().as_raw(), manager.into_raw()) };
136 Ok::<_, Box<dyn std::error::Error>>(config)
137 })
138 .unwrap()
139 }
140}
141
142fn check_scan_permission() -> Result<(), crate::Error> {
143 let has_perm = if android_api_level() >= 31 {
144 if android_has_permission("android.permission.BLUETOOTH_SCAN") {
145 if !android_has_permission("android.permission.ACCESS_FINE_LOCATION") {
146 warn!("Please ensure `neverForLocation` is included in `android:usesPermissionFlags`.")
147 }
148 true } else {
150 false
151 }
152 } else if android_api_level() >= 29 {
153 android_has_permission("android.permission.ACCESS_FINE_LOCATION")
154 && android_has_permission("android.permission.BLUETOOTH_ADMIN")
155 } else {
156 (android_has_permission("android.permission.ACCESS_COARSE_LOCATION")
157 || android_has_permission("android.permission.ACCESS_FINE_LOCATION"))
158 && android_has_permission("android.permission.BLUETOOTH_ADMIN")
159 };
160 if !has_perm {
161 return Err(crate::Error::new(
162 ErrorKind::NotAuthorized,
163 None,
164 "Bluetooth scanning permission is not granted",
165 ));
166 }
167 Ok(())
168}
169
170fn check_connection_permission() -> Result<(), crate::Error> {
171 if !android_has_permission(if android_api_level() >= 31 {
172 "android.permission.BLUETOOTH_CONNECT"
173 } else {
174 "android.permission.BLUETOOTH"
175 }) {
176 return Err(crate::Error::new(
177 ErrorKind::NotAuthorized,
178 None,
179 "Bluetooth connection permission is not granted",
180 ));
181 }
182 Ok(())
183}
184
185impl Adapter {
186 pub async fn default() -> Option<Self> {
188 Adapter::with_config(AdapterConfig::default()).await.ok()
189 }
190
191 pub async fn with_config(config: AdapterConfig) -> Result<Self> {
194 unsafe {
195 let vm = VM::from_raw(config.vm);
196 let _ = jni_set_vm(vm);
197
198 let manager: Global<BluetoothManager> = Global::from_raw(vm.into(), config.manager);
199
200 jni_with_env(|env| {
201 let local_manager = manager.as_ref(env);
202 let adapter = local_manager.getAdapter()?.non_null()?;
203 Ok(Self {
204 inner: Arc::new(AdapterInner {
205 adapter: adapter.as_global(),
206 manager: manager.clone(),
207 global_event_receiver: EventReceiver::build()?,
208 request_mtu_on_connect: config.request_mtu_on_connect,
209 allow_multiple_connections: config.allow_multiple_connections,
210 }),
211 })
212 })
213 }
214 }
215
216 pub async fn events(
218 &self,
219 ) -> Result<impl Stream<Item = Result<AdapterEvent>> + Send + Unpin + '_> {
220 Ok(self
221 .inner
222 .global_event_receiver
223 .subscribe()
224 .await?
225 .filter_map(|event| {
226 if let GlobalEvent::AdapterStateChanged(val) = event {
227 match val {
228 BluetoothAdapter::STATE_ON => Some(AdapterEvent::Available),
229 BluetoothAdapter::STATE_OFF => Some(AdapterEvent::Unavailable),
230 _ => None, }
232 } else {
233 None
234 }
235 })
236 .map(Ok))
237 }
238
239 pub async fn wait_available(&self) -> Result<()> {
241 while !self.is_available().await? {
242 let mut events = self.events().await?;
243 while let Some(Ok(event)) = events.next().await {
244 if event == AdapterEvent::Available {
245 return Ok(());
246 }
247 }
248 }
249 Ok(())
250 }
251
252 pub async fn is_available(&self) -> Result<bool> {
254 jni_with_env(|env| {
255 let adapter = self.inner.adapter.as_local(env);
256 adapter.isEnabled().map_err(|e| {
257 Error::new(ErrorKind::Internal, None, format!("isEnabled threw: {e:?}"))
258 })
259 })
260 }
261
262 pub async fn open_device(&self, id: &DeviceId) -> Result<Device> {
264 if let Some(dev) = self
265 .connected_devices()
266 .await?
267 .into_iter()
268 .find(|d| &d.id() == id)
269 {
270 return Ok(dev);
271 }
272 jni_with_env(|env| {
273 let adapter = self.inner.adapter.as_local(env);
274 let device = adapter
275 .getRemoteDevice_String(JString::from_env_str(env, &id.0))
276 .map_err(|e| {
277 Error::new(
278 ErrorKind::Internal,
279 None,
280 format!("getRemoteDevice threw: {e:?}"),
281 )
282 })?
283 .non_null()?;
284 Ok(Device {
285 id: id.clone(),
286 device: device.as_global(),
287 connection: CachedWeak::new(),
288 once_connected: Arc::new(OnceLock::new()),
289 })
290 })
291 }
292
293 pub async fn connected_devices(&self) -> Result<Vec<Device>> {
300 check_connection_permission()?;
301 if self.inner.allow_multiple_connections {
302 let mut device_items = Vec::new();
303 jni_with_env(|env| {
304 let manager = self.inner.manager.as_ref(env);
305 let devices = manager
306 .getConnectedDevices(BluetoothProfile::GATT)?
307 .non_null()?;
308 let iter_devices = JavaIterator(devices.iterator()?.non_null()?);
309
310 for device in iter_devices.filter_map(|dev| dev.cast::<BluetoothDevice>().ok()) {
311 let id = DeviceId(
312 device
313 .getAddress()?
314 .non_null()?
315 .to_string_lossy()
316 .trim()
317 .to_string(),
318 );
319 let device_item = Device {
320 id,
321 device: device.as_global(),
322 connection: CachedWeak::new(),
323 once_connected: Arc::new(OnceLock::from(())),
325 };
326 device_items.push(device_item);
327 }
328 Ok::<_, crate::Error>(())
329 })?;
330 for device_item in &device_items {
331 if GattTree::find_connection(&device_item.id).is_none() {
332 self.connect_device(device_item).await?;
333 }
334 }
335 Ok(device_items)
336 } else {
337 GattTree::registered_devices()
338 }
339 }
340
341 pub async fn connected_devices_with_services(
343 &self,
344 service_ids: &[Uuid],
345 ) -> Result<Vec<Device>> {
346 let mut devices_found = Vec::new();
347 for device in self.connected_devices().await? {
348 device.discover_services().await?;
349 let device_services = device.services().await?;
350 if service_ids
351 .iter()
352 .any(|&id| device_services.iter().any(|serv| serv.uuid() == id))
353 {
354 devices_found.push(device);
355 }
356 }
357 Ok(devices_found)
358 }
359
360 pub async fn scan<'a>(
369 &'a self,
370 service_ids: &'a [Uuid],
371 ) -> Result<impl Stream<Item = AdvertisingDevice> + Send + Unpin + 'a> {
372 check_scan_permission()?;
373 let (start_receiver, stream) = jni_with_env(|env| {
374 let (start_sender, start_receiver) = async_channel::bounded(1);
375 let (device_sender, device_receiver) = async_channel::bounded(16);
376
377 let callback = ScanCallback::new_proxy(
378 env,
379 Arc::new(ScanCallbackProxy {
380 device_sender,
381 start_sender,
382 }),
383 )?;
384 let callback_global = callback.as_global();
385
386 let adapter = self.inner.adapter.as_ref(env);
387 let adapter_global = adapter.as_global();
388 let adapter = Monitor::new(&adapter);
389 let scanner = adapter.getBluetoothLeScanner()?.non_null()?;
390 let scanner_global = scanner.as_global();
391
392 let settings_builder = ScanSettings_Builder::new(env)?;
393 settings_builder.setScanMode(ScanSettings::SCAN_MODE_LOW_LATENCY)?;
394 let settings = settings_builder.build()?.non_null()?;
395
396 if !service_ids.is_empty() {
397 let filter_builder = ScanFilter_Builder::new(env)?;
398 let filter_list = java::util::ArrayList::new(env)?;
399 for uuid in service_ids {
400 let uuid_string = JString::from_env_str(env, uuid.to_string());
401 let parcel_uuid = ParcelUuid::fromString(env, uuid_string)?;
402 filter_builder.setServiceUuid_ParcelUuid(parcel_uuid)?;
403 let filter = filter_builder.build()?.non_null()?;
404 filter_list.add_Object(filter)?;
405 }
406 scanner.startScan_List_ScanSettings_ScanCallback(
407 filter_list,
408 settings,
409 callback,
410 )?;
411 } else {
412 scanner.startScan_List_ScanSettings_ScanCallback(Null, settings, callback)?;
413 };
414
415 let guard = defer(move || {
416 jni_with_env(|env| {
417 let callback = callback_global.as_ref(env);
418 let scanner = scanner_global.as_ref(env);
419 if adapter_global.as_ref(env).isEnabled().unwrap_or(false) {
420 match scanner.stopScan_ScanCallback(callback) {
421 Ok(()) => debug!("stopped scan"),
422 Err(e) => warn!("failed to stop scan: {:?}", e),
423 };
424 }
425 });
426 });
427
428 Ok::<_, crate::Error>((
429 start_receiver,
430 Box::pin(device_receiver).map(move |adv_dev| {
431 let _guard = &guard;
432 adv_dev
433 }),
434 ))
435 })?;
436
437 #[rustfmt::skip]
438 let stream = StreamUntil::create(
439 stream,
440 self.inner.global_event_receiver.subscribe().await?,
441 |event| {
442 matches!(
443 event,
444 GlobalEvent::DiscoveryFinished
445 | GlobalEvent::AdapterStateChanged(BluetoothAdapter::STATE_OFF)
446 )
447 }
448 );
449
450 match start_receiver.recv().await {
452 Ok(Ok(())) => Ok(stream),
453 Ok(Err(e)) => Err(e),
454 Err(e) => Err(Error::new(
455 ErrorKind::Internal,
456 None,
457 format!("receiving failed while waiting for start: {e:?}"),
458 )),
459 }
460 }
461
462 pub async fn discover_devices<'a>(
469 &'a self,
470 services: &'a [Uuid],
471 ) -> Result<impl Stream<Item = Result<Device>> + Send + Unpin + 'a> {
472 let connected = stream::iter(self.connected_devices_with_services(services).await?).map(Ok);
473
474 let advertising = Box::pin(stream::try_unfold(None, |state| async {
476 let mut stream = match state {
477 Some(stream) => stream,
478 None => self.scan(services).await?,
479 };
480 Ok(stream.next().await.map(|x| (x.device, Some(stream))))
481 }));
482
483 Ok(connected.chain(advertising))
484 }
485
486 pub async fn connect_device(&self, device: &Device) -> Result<()> {
488 check_connection_permission()?;
489 let _conn_lock = CONN_MUTEX.lock().await;
490 if device.is_connected().await {
491 return Ok(());
492 }
493 if !self.inner.allow_multiple_connections && self.is_actually_connected(&device.id())? {
494 return Err(Error::new(
495 ErrorKind::ConnectionFailed,
496 None,
497 "device is connected outside the current `android_ble` library",
498 ));
499 }
500 let callback_hdl = BluetoothGattCallbackProxy::new(device.id());
501 jni_with_env(|env| {
502 let adapter = self.inner.adapter.as_ref(env);
503 let _lock = Monitor::new(&adapter);
504 let device_obj = device.device.as_local(env);
505 let proxy = BluetoothGattCallback::new_proxy(env, callback_hdl.clone())?;
506 let gatt = device_obj
507 .connectGatt_Context_boolean_BluetoothGattCallback(
508 android_context().as_ref(env),
509 false,
510 proxy,
511 )
512 .map_err(|e| {
513 Error::new(
514 ErrorKind::Internal,
515 None,
516 format!("connectGatt threw: {e:?}"),
517 )
518 })?
519 .non_null()?
520 .as_global();
521 GattTree::register_connection(
522 &device.id(),
523 gatt,
524 &callback_hdl,
525 &self.inner.global_event_receiver,
526 );
527 Ok::<_, crate::Error>(())
528 })?;
529 if !self.is_actually_connected(&device.id())? {
530 GattTree::wait_connection_available(&device.id()).await?;
531 }
532 if self.inner.request_mtu_on_connect {
533 let conn = GattTree::check_connection(&device.id())?;
534 let mtu_lock = conn.mtu_changed_received.lock().await;
535 jni_with_env(|env| {
536 let gatt = conn.gatt.as_ref(env);
537 let gatt = Monitor::new(&gatt);
538 gatt.requestMtu(517)?;
539 Ok::<_, crate::Error>(())
540 })?;
541 let _ = mtu_lock.wait_unlock().await;
542 }
543 if device.once_connected.get().is_some() {
545 let _ = device.discover_services().await?;
546 }
547 let _ = device.once_connected.set(());
548 Ok(())
549 }
550
551 pub async fn disconnect_device(&self, device: &Device) -> Result<()> {
555 let _conn_lock = CONN_MUTEX.lock().await;
556 let Ok(conn) = device.get_connection() else {
557 return Ok(());
558 };
559 jni_with_env(|env| {
560 let adapter = self.inner.adapter.as_ref(env);
561 let _lock = Monitor::new(&adapter);
562 let gatt = &conn.gatt.as_ref(env);
563 let gatt = Monitor::new(gatt);
564 gatt.disconnect().map_err(|e| {
565 Error::new(
566 ErrorKind::Internal,
567 None,
568 format!("BluetoothGatt.disconnect() threw: {e:?}"),
569 )
570 })?;
571 Ok::<_, crate::Error>(())
572 })?;
573 GattTree::deregister_connection(&device.id());
574 Ok(())
575 }
576
577 pub async fn device_connection_events<'a>(
584 &'a self,
585 device: &'a Device,
586 ) -> Result<impl Stream<Item = ConnectionEvent> + Send + Unpin + 'a> {
587 Ok(StreamUntil::create(
588 GattTree::connection_events()
589 .await
590 .filter_map(|(dev_id, ev)| {
591 if dev_id == device.id() {
592 Some(ev)
593 } else {
594 None
595 }
596 }),
597 self.events().await?,
598 |e| matches!(e, Ok(AdapterEvent::Unavailable)),
599 ))
600 }
601
602 pub(crate) fn is_actually_connected(&self, dev_id: &DeviceId) -> Result<bool> {
604 jni_with_env(|env| {
605 let manager = self.inner.manager.as_ref(env);
606 let devices = manager
607 .getConnectedDevices(BluetoothProfile::GATT)?
608 .non_null()?;
609 let iter_devices = JavaIterator(devices.iterator()?.non_null()?);
610 for device in iter_devices.filter_map(|dev| dev.cast::<BluetoothDevice>().ok()) {
611 if dev_id.0 == device.getAddress()?.non_null()?.to_string_lossy().trim() {
612 return Ok(true);
613 }
614 }
615 Ok(false)
616 })
617 }
618}
619
620impl PartialEq for Adapter {
621 fn eq(&self, _other: &Self) -> bool {
622 true
623 }
624}
625
626impl Eq for Adapter {}
627
628impl std::hash::Hash for Adapter {
629 fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
630}
631
632impl std::fmt::Debug for Adapter {
633 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
634 f.debug_tuple("Adapter").finish()
635 }
636}
637
638struct ScanCallbackProxy {
639 start_sender: async_channel::Sender<Result<()>>,
640 device_sender: async_channel::Sender<AdvertisingDevice>,
641}
642
643impl super::callback::ScanCallbackProxy for ScanCallbackProxy {
644 fn onScanFailed<'env>(&self, _env: Env<'env>, error_code: i32) {
645 let e = Error::new(
646 ErrorKind::Internal,
647 None,
648 format!("Scan failed to start with error code {error_code}"),
649 );
650 if let Err(e) = self.start_sender.try_send(Err(e)) {
651 warn!("onScanFailed failed to send error: {e:?}");
652 }
653 }
654
655 fn onBatchScanResults<'env>(
656 &self,
657 env: Env<'env>,
658 scan_results: Option<Ref<'env, super::bindings::java::util::List>>,
659 ) {
660 let Some(scan_results) = scan_results else {
661 warn!("onBatchScanResults: ignoring null scan_results");
662 return;
663 };
664
665 if let Err(e) = self.on_scan_result_list(env, &scan_results) {
666 warn!("onBatchScanResults failed: {e:?}");
667 }
668 }
669
670 fn onScanResult<'env>(
671 &self,
672 env: Env<'env>,
673 _callback_type: i32,
674 scan_result: Option<Ref<'env, ScanResult>>,
675 ) {
676 let Some(scan_result) = scan_result else {
677 warn!("onScanResult: ignoring null scan_result");
678 return;
679 };
680
681 if let Err(e) = self.on_scan_result(env, &scan_result) {
682 warn!("onScanResult failed: {e:?}");
683 }
684 }
685}
686
687impl ScanCallbackProxy {
688 fn on_scan_result_list(
689 &self,
690 env: Env<'_>,
691 scan_results: &Ref<super::bindings::java::util::List>,
692 ) -> Result<()> {
693 for scan_result in JavaIterator(scan_results.iterator()?.non_null()?) {
694 let scan_result: Local<ScanResult> = scan_result.cast()?;
695 self.on_scan_result(env, &scan_result.as_ref())?;
696 }
697 Ok(())
698 }
699
700 fn on_scan_result(&self, _env: Env<'_>, scan_result: &Ref<ScanResult>) -> Result<()> {
701 let scan_record = scan_result.getScanRecord()?.non_null()?;
702 let device = scan_result.getDevice()?.non_null()?;
703
704 let address = device
705 .getAddress()?
706 .non_null()?
707 .to_string_lossy()
708 .trim()
709 .to_string();
710 let rssi = scan_result.getRssi()?;
711 let is_connectable = if android_api_level() >= 26 {
712 scan_result.isConnectable()?
713 } else {
714 true };
716 let local_name = scan_record.getDeviceName()?.map(|s| s.to_string_lossy());
717 let tx_power_level = scan_record.getTxPowerLevel()?;
718
719 let mut services = Vec::new();
721 if let Some(uuids) = scan_record.getServiceUuids()? {
722 for uuid in JavaIterator(uuids.iterator()?.non_null()?) {
723 services.push(Uuid::from_andriod_parcel(uuid.cast()?)?)
724 }
725 }
726
727 let mut service_data = HashMap::new();
729 let sd = scan_record.getServiceData()?.non_null()?;
730 let sd = sd.entrySet()?.non_null()?;
731 for entry in JavaIterator(sd.iterator()?.non_null()?) {
732 let entry: Local<Map_Entry> = entry.cast()?;
733 let key: Local<ParcelUuid> = entry.getKey()?.non_null()?.cast()?;
734 let val: Local<ByteArray> = entry.getValue()?.non_null()?.cast()?;
735 service_data.insert(Uuid::from_andriod_parcel(key)?, val.as_vec_u8());
736 }
737
738 let mut manufacturer_data = None;
740 let msd = scan_record.getManufacturerSpecificData()?.non_null()?;
741 if msd.size()? != 0 {
744 let val: Local<'_, ByteArray> = msd.valueAt(0)?.non_null()?.cast()?;
745 manufacturer_data = Some(ManufacturerData {
746 company_id: msd.keyAt(0)? as _,
747 data: val.as_vec_u8(),
748 });
749 }
750
751 let device_id = DeviceId(address);
752
753 let d = AdvertisingDevice {
754 device: Device {
755 id: device_id.clone(),
756 device: device.as_global(),
757 connection: CachedWeak::new(),
758 once_connected: Arc::new(if GattTree::find_connection(&device_id).is_none() {
759 OnceLock::new()
760 } else {
761 OnceLock::from(()) }),
763 },
764 adv_data: AdvertisementData {
765 is_connectable,
766 local_name,
767 manufacturer_data, service_data,
769 services,
770 tx_power_level: Some(tx_power_level as _),
771 },
772 rssi: Some(rssi as _),
773 };
774
775 self.start_sender.try_send(Ok(())).ok();
776 self.device_sender.try_send(d).ok();
777
778 Ok(())
779 }
780}