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 } ;
1
8
use futures_core:: Stream ;
2
9
use futures_lite:: { stream, StreamExt } ;
10
+ use java_spaghetti:: { Arg , ByteArray , Env , Global , Local , PrimitiveArray , VM } ;
11
+ use tracing:: { debug, warn} ;
3
12
use uuid:: Uuid ;
4
13
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
+ }
6
31
7
32
#[ derive( Clone ) ]
8
- pub struct AdapterImpl { }
33
+ pub struct AdapterImpl {
34
+ inner : Arc < AdapterInner > ,
35
+ }
36
+
9
37
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
+ } )
12
55
}
13
56
14
57
pub ( crate ) async fn events ( & self ) -> Result < impl Stream < Item = Result < AdapterEvent > > + Send + Unpin + ' _ > {
@@ -35,7 +78,29 @@ impl AdapterImpl {
35
78
& ' a self ,
36
79
_services : & ' a [ Uuid ] ,
37
80
) -> 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
+ } )
39
104
}
40
105
41
106
pub async fn discover_devices < ' a > (
@@ -91,3 +156,161 @@ impl std::fmt::Debug for AdapterImpl {
91
156
f. debug_tuple ( "Adapter" ) . finish ( )
92
157
}
93
158
}
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