Skip to main content

android_ble/
adapter.rs

1// Some portions of this code is orginally written by <https://github.com/Dirbaio>.
2
3use 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/// The system’s Bluetooth adapter interface.
40#[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
56/// Configuration for creating an interface to the default Bluetooth adapter of the system.
57///
58/// By deafult, [ndk-context](https://docs.rs/ndk-context/0.1.1/ndk_context) is used for
59/// obtaining the JNI `JavaVM` pointer.
60///
61/// TODO: add an option for enforcing all operations of a device to lock the same mutex.
62pub struct AdapterConfig {
63    /// - `vm` must be a valid JNI `JavaVM` pointer to a VM that will stay alive for the current
64    ///   native library's lifetime. This is true for any library used by an Android application.
65    vm: *mut java_spaghetti::sys::JavaVM,
66    /// `manager` must be a valid global reference to an `android.bluetooth.BluetoothManager`
67    /// instance, from the `java_vm` VM.
68    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    /// Creates a config for the default Bluetooth adapter for the system.
78    ///
79    /// You do not need to do this if you are using the `android_activity` crate for a native application.
80    ///
81    /// # Safety
82    ///
83    /// - `java_vm` must be a valid JNI `JavaVM` pointer to a VM that will stay alive for the current native
84    ///   library's lifetime. This is true for any library used by an Android application.
85    /// - `bluetooth_manager` must be a valid global reference to an `android.bluetooth.BluetoothManager`
86    ///   instance, from the `java_vm` VM.
87    /// - The `Adapter` takes ownership of the global reference and will delete it with the `DeleteGlobalRef`
88    ///   JNI call when dropped. You must not do that yourself.
89    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    /// If enabled, this library will request the BLE ATT MTU to 517 bytes during [Adapter::connect_device].
102    /// See <https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517>.
103    ///
104    /// If disabled, [crate::Characteristic::max_write_len] may always return `18`.
105    ///
106    /// This is enabled by default; disable it if the firmware of the device to be connected is problematic.
107    pub fn request_mtu_on_connect(mut self, enabled: bool) -> Self {
108        self.request_mtu_on_connect = enabled;
109        self
110    }
111
112    /// If enabled, connections with devices already connected outside this library instance will
113    /// be permitted. Note that another `android.bluetooth.BluetoothGatt` object will not be created
114    /// if the device is already connected in the current library instance.
115    ///
116    /// This is enabled by default; this should be okay on well-implemented Android API implementations,
117    /// but disabling it might improve Android compatibility.
118    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 // XXX
149        } 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    /// Creates an interface to a Bluetooth adapter using the default config.
187    pub async fn default() -> Option<Self> {
188        Adapter::with_config(AdapterConfig::default()).await.ok()
189    }
190
191    /// Creates an interface to a Bluetooth adapter. The `vm` pointer will be ignored
192    /// if this has been called previously.
193    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    /// A stream of [AdapterEvent] which allows the application to identify when the adapter is enabled or disabled.
217    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, // XXX: process "turning on" and "turning off" events
231                    }
232                } else {
233                    None
234                }
235            })
236            .map(Ok))
237    }
238
239    /// Asynchronously blocks until the adapter is available.
240    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    /// Check if the adapter is available.
253    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    /// Attempts to create the device identified by `id`.
263    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    /// Finds all connected Bluetooth LE devices.
294    ///
295    /// NOTE: there might be BLE devices connected outside this library.
296    /// If [AdapterConfig::allow_multiple_connections] is set to true, this method will call
297    /// `BluetoothManager.getConnectedDevices()` and ensure GATT connections are created
298    /// for them in this library instance.
299    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                        // NOTE: this makes the `connect_device` called later to discover services as if it's reconnected.
324                        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    /// Finds all connected devices providing any service in `service_ids`.
342    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    /// Starts scanning for Bluetooth advertising packets.
361    ///
362    /// Returns a stream of [`AdvertisingDevice`] structs which contain the data from the advertising packet and the
363    /// [`Device`] which sent it. Scanning is automatically stopped when the stream is dropped. Inclusion of duplicate
364    /// packets is a platform-specific implementation detail.
365    ///
366    /// If `service_ids` is not empty, returns advertisements including at least one GATT service with a UUID in
367    /// `services`. Otherwise returns all advertisements.
368    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        // Wait for scan started or failed.
451        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    /// Finds Bluetooth devices providing any service in `services`.
463    ///
464    /// Returns a stream of [`Device`] structs with matching connected devices returned first. If the stream is not
465    /// dropped before all matching connected devices are consumed then scanning will begin for devices advertising any
466    /// of the `services`. Scanning will continue until the stream is dropped. Inclusion of duplicate devices is a
467    /// platform-specific implementation detail.
468    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        // try_unfold is used to ensure we do not start scanning until the connected devices have been consumed
475        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    /// Connects to the [`Device`].
487    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        // validates GATT tree API objects again upon reconnection
544        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    /// Disconnects from the [`Device`].
552    ///
553    /// XXX: manage to call this internally when all API wrapper objects for the device are dropped.
554    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    /// Monitors a device for connection/disconnection events.
578    ///
579    /// This monitors only devices connected/disconnected in this library instance,
580    /// even if [AdapterConfig::allow_multiple_connections] is set to true.
581    ///
582    /// This does not work with random address devices.
583    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    // NOTE: this returns true even if the device is connected outside this crate.
603    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 // XXX: try to check `eventType` via `ScanResult.toString()`
715        };
716        let local_name = scan_record.getDeviceName()?.map(|s| s.to_string_lossy());
717        let tx_power_level = scan_record.getTxPowerLevel()?;
718
719        // Services
720        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        // Service data
728        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        // Manufacturer data
739        let mut manufacturer_data = None;
740        let msd = scan_record.getManufacturerSpecificData()?.non_null()?;
741        // TODO: there can be multiple manufacturer data entries, but the API (compatible with bluest)
742        // only supports one. So grab just the first.
743        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(()) // NOTE: this is unlikely to happen
762                }),
763            },
764            adv_data: AdvertisementData {
765                is_connectable,
766                local_name,
767                manufacturer_data, // TODO, SparseArray is cursed.
768                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}