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