Skip to content

Commit 809350c

Browse files
committed
android: add scan.
1 parent 4176e29 commit 809350c

File tree

9 files changed

+9142
-5
lines changed

9 files changed

+9142
-5
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ windows = { version = "0.48.0", features = [
5454
bluer = { version = "0.16.1", features = ["bluetoothd"] }
5555
tokio = { version = "1.20.1", features = ["rt-multi-thread"] }
5656

57+
[target.'cfg(target_os = "android")'.dependencies]
58+
java-spaghetti = "0.1.0"
59+
futures-channel = "0.3.24"
60+
5761
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
5862
async-broadcast = "0.5.1"
5963
objc = "0.2.7"

src/adapter.rs

+16
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,24 @@ use crate::{sys, AdapterEvent, AdvertisingDevice, ConnectionEvent, Device, Devic
1111
pub struct Adapter(sys::adapter::AdapterImpl);
1212

1313
impl Adapter {
14+
/// Creates an interface to the default Bluetooth adapter for the system.
15+
///
16+
/// # Safety
17+
///
18+
/// - `java_vm` must be a valid JNI `JavaVM` pointer to a VM that will stay alive for the entire duration the `Adapter` or any structs obtained from it are live.
19+
/// - `bluetooth_manager` must be a valid global reference to an `android.bluetooth.BluetoothManager` instance, from the `java_vm` VM.
20+
/// - The `Adapter` takes ownership of the global reference and will delete it with the `DeleteGlobalRef` JNI call when dropped. You must not do that yourself.
21+
#[cfg(target_os = "android")]
22+
pub unsafe fn new(
23+
java_vm: *mut java_spaghetti::sys::JavaVM,
24+
bluetooth_manager: java_spaghetti::sys::jobject,
25+
) -> Result<Self> {
26+
sys::adapter::AdapterImpl::new(java_vm, bluetooth_manager).map(Self)
27+
}
28+
1429
/// Creates an interface to the default Bluetooth adapter for the system
1530
#[inline]
31+
#[cfg(not(target_os = "android"))]
1632
pub async fn default() -> Option<Self> {
1733
sys::adapter::AdapterImpl::default().await.map(Adapter)
1834
}

src/android/adapter.rs

+228-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,57 @@
1+
use std::collections::HashMap;
2+
use std::pin::Pin;
3+
use std::sync::atomic::{AtomicI32, Ordering};
4+
use std::sync::{Arc, Mutex};
5+
use std::task::{Context, Poll};
6+
7+
use futures_channel::mpsc::{Receiver, Sender};
18
use futures_core::Stream;
29
use futures_lite::{stream, StreamExt};
10+
use java_spaghetti::{Arg, ByteArray, Env, Global, Local, PrimitiveArray, VM};
11+
use tracing::{debug, warn};
312
use uuid::Uuid;
413

5-
use crate::{AdapterEvent, AdvertisingDevice, ConnectionEvent, Device, DeviceId, Result};
14+
use super::bindings::android::bluetooth::le::{BluetoothLeScanner, ScanResult};
15+
use super::bindings::android::bluetooth::{BluetoothAdapter, BluetoothManager};
16+
use super::bindings::android::os::ParcelUuid;
17+
use super::bindings::com::github::alexmoon::bluest::android::BluestScanCallback;
18+
use super::device::DeviceImpl;
19+
use super::{JavaIterator, OptionExt};
20+
use crate::android::bindings::java::util::Map_Entry;
21+
use crate::util::defer;
22+
use crate::{
23+
AdapterEvent, AdvertisementData, AdvertisingDevice, ConnectionEvent, Device, DeviceId, ManufacturerData, Result,
24+
};
25+
26+
struct AdapterInner {
27+
manager: Global<BluetoothManager>,
28+
_adapter: Global<BluetoothAdapter>,
29+
le_scanner: Global<BluetoothLeScanner>,
30+
}
631

732
#[derive(Clone)]
8-
pub struct AdapterImpl {}
33+
pub struct AdapterImpl {
34+
inner: Arc<AdapterInner>,
35+
}
36+
937
impl AdapterImpl {
10-
pub async fn default() -> Option<Self> {
11-
Some(Self {})
38+
pub unsafe fn new(vm: *mut java_spaghetti::sys::JavaVM, manager: java_spaghetti::sys::jobject) -> Result<Self> {
39+
let vm = VM::from_raw(vm);
40+
let manager: Global<BluetoothManager> = Global::from_raw(vm, manager);
41+
42+
vm.with_env(|env| {
43+
let local_manager = manager.as_ref(env);
44+
let adapter = local_manager.getAdapter()?.non_null()?;
45+
let le_scanner = adapter.getBluetoothLeScanner()?.non_null()?;
46+
47+
Ok(Self {
48+
inner: Arc::new(AdapterInner {
49+
_adapter: adapter.as_global(),
50+
le_scanner: le_scanner.as_global(),
51+
manager: manager.clone(),
52+
}),
53+
})
54+
})
1255
}
1356

1457
pub(crate) async fn events(&self) -> Result<impl Stream<Item = Result<AdapterEvent>> + Send + Unpin + '_> {
@@ -35,7 +78,29 @@ impl AdapterImpl {
3578
&'a self,
3679
_services: &'a [Uuid],
3780
) -> Result<impl Stream<Item = AdvertisingDevice> + Send + Unpin + 'a> {
38-
Ok(stream::empty()) // TODO
81+
self.inner.manager.vm().with_env(|env| {
82+
let receiver = SCAN_CALLBACKS.allocate();
83+
let callback = BluestScanCallback::new(env, receiver.id)?;
84+
let callback_global = callback.as_global();
85+
let scanner = self.inner.le_scanner.as_ref(env);
86+
scanner.startScan_ScanCallback(&**callback)?;
87+
88+
let guard = defer(move || {
89+
self.inner.manager.vm().with_env(|env| {
90+
let callback = callback_global.as_ref(env);
91+
let scanner = self.inner.le_scanner.as_ref(env);
92+
match scanner.stopScan_ScanCallback(&**callback) {
93+
Ok(()) => debug!("stopped scan"),
94+
Err(e) => warn!("failed to stop scan: {:?}", e),
95+
};
96+
});
97+
});
98+
99+
Ok(receiver.map(move |x| {
100+
let _guard = &guard;
101+
x
102+
}))
103+
})
39104
}
40105

41106
pub async fn discover_devices<'a>(
@@ -91,3 +156,161 @@ impl std::fmt::Debug for AdapterImpl {
91156
f.debug_tuple("Adapter").finish()
92157
}
93158
}
159+
160+
static SCAN_CALLBACKS: CallbackRouter<AdvertisingDevice> = CallbackRouter::new();
161+
162+
struct CallbackRouter<T: Send + 'static> {
163+
map: Mutex<Option<HashMap<i32, Sender<T>>>>,
164+
next_id: AtomicI32,
165+
}
166+
167+
impl<T: Send + 'static> CallbackRouter<T> {
168+
const fn new() -> Self {
169+
Self {
170+
map: Mutex::new(None),
171+
next_id: AtomicI32::new(0),
172+
}
173+
}
174+
175+
fn allocate(&'static self) -> CallbackReceiver<T> {
176+
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
177+
let (sender, receiver) = futures_channel::mpsc::channel(16);
178+
self.map
179+
.lock()
180+
.unwrap()
181+
.get_or_insert_with(Default::default)
182+
.insert(id, sender);
183+
184+
CallbackReceiver {
185+
router: self,
186+
id,
187+
receiver,
188+
}
189+
}
190+
191+
fn callback(&'static self, id: i32, val: T) {
192+
if let Some(sender) = self.map.lock().unwrap().as_mut().unwrap().get_mut(&id) {
193+
if let Err(e) = sender.try_send(val) {
194+
warn!("failed to send scan callback: {:?}", e)
195+
}
196+
}
197+
}
198+
}
199+
200+
struct CallbackReceiver<T: Send + 'static> {
201+
router: &'static CallbackRouter<T>,
202+
id: i32,
203+
receiver: Receiver<T>,
204+
}
205+
206+
impl<T: Send + 'static> Stream for CallbackReceiver<T> {
207+
type Item = T;
208+
209+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
210+
// safety: this is just a manually-written pin projection.
211+
let receiver = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().receiver) };
212+
receiver.poll_next(cx)
213+
}
214+
}
215+
216+
impl<T: Send> Drop for CallbackReceiver<T> {
217+
fn drop(&mut self) {
218+
self.router.map.lock().unwrap().as_mut().unwrap().remove(&self.id);
219+
}
220+
}
221+
222+
#[no_mangle]
223+
pub extern "system" fn Java_com_github_alexmoon_bluest_android_BluestScanCallback_nativeOnScanResult(
224+
env: Env<'_>,
225+
_class: *mut (), // self class, ignore
226+
id: i32,
227+
callback_type: i32,
228+
scan_result: Arg<ScanResult>,
229+
) {
230+
if let Err(e) = on_scan_result(env, id, callback_type, scan_result) {
231+
warn!("on_scan_result failed: {:?}", e);
232+
}
233+
}
234+
235+
fn convert_uuid(uuid: Local<'_, ParcelUuid>) -> Result<Uuid> {
236+
let uuid = uuid.getUuid()?.non_null()?;
237+
let lsb = uuid.getLeastSignificantBits()? as u64;
238+
let msb = uuid.getMostSignificantBits()? as u64;
239+
Ok(Uuid::from_u64_pair(msb, lsb))
240+
}
241+
242+
#[no_mangle]
243+
fn on_scan_result(env: Env<'_>, id: i32, callback_type: i32, scan_result: Arg<ScanResult>) -> Result<()> {
244+
let scan_result = unsafe { scan_result.into_ref(env) }.non_null()?;
245+
246+
tracing::info!("got callback! {} {}", id, callback_type);
247+
248+
let scan_record = scan_result.getScanRecord()?.non_null()?;
249+
let device = scan_result.getDevice()?.non_null()?;
250+
251+
let address = device.getAddress()?.non_null()?.to_string_lossy();
252+
let rssi = scan_result.getRssi()?;
253+
let is_connectable = scan_result.isConnectable()?;
254+
let local_name = scan_record.getDeviceName()?.map(|s| s.to_string_lossy());
255+
let tx_power_level = scan_record.getTxPowerLevel()?;
256+
257+
// Services
258+
let mut services = Vec::new();
259+
if let Some(uuids) = scan_record.getServiceUuids()? {
260+
for uuid in JavaIterator(uuids.iterator()?.non_null()?) {
261+
services.push(convert_uuid(uuid.cast()?)?)
262+
}
263+
}
264+
265+
// Service data
266+
let mut service_data = HashMap::new();
267+
let sd = scan_record.getServiceData()?.non_null()?;
268+
let sd = sd.entrySet()?.non_null()?;
269+
for entry in JavaIterator(sd.iterator()?.non_null()?) {
270+
let entry: Local<Map_Entry> = entry.cast()?;
271+
let key: Local<ParcelUuid> = entry.getKey()?.non_null()?.cast()?;
272+
let val: Local<ByteArray> = entry.getValue()?.non_null()?.cast()?;
273+
service_data.insert(convert_uuid(key)?, val.as_vec().into_iter().map(|i| i as u8).collect());
274+
}
275+
276+
// Manufacturer data
277+
let mut manufacturer_data = None;
278+
let msd = scan_record.getManufacturerSpecificData()?.non_null()?;
279+
// TODO there can be multiple manufacturer data entries, but the bluest API only supports one. So grab just the first.
280+
if msd.size()? != 0 {
281+
let val: Local<'_, ByteArray> = msd.valueAt(0)?.non_null()?.cast()?;
282+
manufacturer_data = Some(ManufacturerData {
283+
company_id: msd.keyAt(0)? as _,
284+
data: val.as_vec().into_iter().map(|i| i as u8).collect(),
285+
});
286+
}
287+
288+
let device_id = DeviceId(address);
289+
290+
let d = AdvertisingDevice {
291+
device: Device(DeviceImpl { id: device_id }),
292+
adv_data: AdvertisementData {
293+
is_connectable,
294+
local_name,
295+
manufacturer_data, // TODO, SparseArray is cursed.
296+
service_data,
297+
services,
298+
tx_power_level: Some(tx_power_level as _),
299+
},
300+
rssi: Some(rssi as _),
301+
};
302+
SCAN_CALLBACKS.callback(id, d);
303+
304+
Ok(())
305+
}
306+
307+
#[no_mangle]
308+
pub extern "system" fn Java_com_github_alexmoon_bluest_android_BluestScanCallback_nativeOnScanFailed(
309+
_env: Env<'_>,
310+
_class: *mut (), // self class, ignore
311+
id: i32,
312+
error_code: i32,
313+
) {
314+
tracing::error!("got scan fail! {} {}", id, error_code);
315+
todo!()
316+
}

0 commit comments

Comments
 (0)