expo-beacon 0.9.3 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -59
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/java/expo/modules/beacon/BeaconApiForwarder.kt +44 -31
- package/android/src/main/java/expo/modules/beacon/BeaconConstants.kt +20 -0
- package/android/src/main/java/expo/modules/beacon/BeaconEventLogger.kt +2 -1
- package/android/src/main/java/expo/modules/beacon/BeaconEventReceiver.kt +34 -88
- package/android/src/main/java/expo/modules/beacon/BeaconForegroundService.kt +257 -264
- package/android/src/main/java/expo/modules/beacon/BootReceiver.kt +20 -8
- package/android/src/main/java/expo/modules/beacon/CarPlayMonitor.kt +17 -9
- package/android/src/main/java/expo/modules/beacon/ExpoBeaconModule.kt +222 -223
- package/build/ExpoBeacon.types.d.ts +19 -18
- package/build/ExpoBeacon.types.d.ts.map +1 -1
- package/build/ExpoBeacon.types.js.map +1 -1
- package/build/ExpoBeaconModule.d.ts +35 -8
- package/build/ExpoBeaconModule.d.ts.map +1 -1
- package/build/ExpoBeaconModule.js +4 -0
- package/build/ExpoBeaconModule.js.map +1 -1
- package/build/ExpoBeaconModule.web.d.ts +2 -41
- package/build/ExpoBeaconModule.web.d.ts.map +1 -1
- package/build/ExpoBeaconModule.web.js +18 -5
- package/build/ExpoBeaconModule.web.js.map +1 -1
- package/build/hooks/useBeacon.d.ts +132 -0
- package/build/hooks/useBeacon.d.ts.map +1 -0
- package/build/hooks/useBeacon.js +268 -0
- package/build/hooks/useBeacon.js.map +1 -0
- package/build/hooks/useCarPlay.d.ts +46 -0
- package/build/hooks/useCarPlay.d.ts.map +1 -0
- package/build/hooks/useCarPlay.js +88 -0
- package/build/hooks/useCarPlay.js.map +1 -0
- package/build/index.d.ts +5 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/ios/BeaconApiForwarder.swift +6 -9
- package/ios/BluetoothDelegate.swift +2 -4
- package/ios/CarPlayMonitor.swift +26 -22
- package/ios/ExpoBeaconConstants.swift +25 -0
- package/ios/ExpoBeaconModule+Eddystone.swift +30 -14
- package/ios/ExpoBeaconModule+EventLogging.swift +7 -0
- package/ios/ExpoBeaconModule+Monitoring.swift +117 -6
- package/ios/ExpoBeaconModule+Permissions.swift +13 -33
- package/ios/ExpoBeaconModule+Scanning.swift +80 -9
- package/ios/ExpoBeaconModule+Storage.swift +66 -3
- package/ios/ExpoBeaconModule+Timers.swift +135 -110
- package/ios/ExpoBeaconModule.swift +242 -362
- package/package.json +1 -1
- package/plugin/build/index.d.ts.map +1 -1
- package/plugin/build/index.js +5 -13
- package/plugin/build/withBeaconAndroid.d.ts +8 -0
- package/plugin/build/withBeaconAndroid.d.ts.map +1 -1
- package/plugin/build/withBeaconAndroid.js +26 -6
- package/plugin/build/withBeaconIOS.d.ts +8 -0
- package/plugin/build/withBeaconIOS.d.ts.map +1 -1
- package/plugin/build/withBeaconIOS.js +32 -5
- package/src/ExpoBeacon.types.ts +21 -20
- package/src/ExpoBeaconModule.ts +39 -8
- package/src/ExpoBeaconModule.web.ts +86 -67
- package/src/hooks/useBeacon.ts +511 -0
- package/src/hooks/useCarPlay.ts +146 -0
- package/src/index.ts +14 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ An Expo module for scanning, pairing, and monitoring **iBeacons** and **Eddyston
|
|
|
8
8
|
| **Pair** | Register specific beacons for persistent tracking — survives app restarts |
|
|
9
9
|
| **Monitor** | Background enter/exit region detection with distance-based filtering |
|
|
10
10
|
| **Distance** | Real-time distance updates (~1/sec) while monitoring |
|
|
11
|
-
| **Timeout** | Fire a one-shot event after a beacon
|
|
11
|
+
| **Timeout** | Fire a one-shot event after a beacon has been out of range for a configured duration |
|
|
12
12
|
| **Event Logging** | Persist every beacon event to a local SQLite database for diagnostics & replay |
|
|
13
13
|
| **Notifications** | Automatic local notifications on region enter/exit, fully customisable |
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ An Expo module for scanning, pairing, and monitoring **iBeacons** and **Eddyston
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| **Android** | [AltBeacon](https://altbeacon.github.io/android-beacon-library/) library + Foreground Service |
|
|
18
18
|
| **iOS** | CoreLocation (iBeacon ranging & monitoring) + CoreBluetooth (Eddystone & wildcard BLE) |
|
|
19
|
-
| **Web** | Not supported (
|
|
19
|
+
| **Web** | Not supported (async methods reject, sync getters return inert defaults, everything else throws) |
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -27,6 +27,9 @@ An Expo module for scanning, pairing, and monitoring **iBeacons** and **Eddyston
|
|
|
27
27
|
- [iOS](#ios)
|
|
28
28
|
- [Android](#android)
|
|
29
29
|
- [Quick Start](#quick-start)
|
|
30
|
+
- [React Hooks](#react-hooks)
|
|
31
|
+
- [useBeacon()](#usebeacon)
|
|
32
|
+
- [useCarPlay()](#usecarplay)
|
|
30
33
|
- [Usage Examples](#usage-examples)
|
|
31
34
|
- [Scanning for iBeacons](#scanning-for-ibeacons)
|
|
32
35
|
- [Scanning for Eddystone Beacons](#scanning-for-eddystone-beacons)
|
|
@@ -61,6 +64,7 @@ An Expo module for scanning, pairing, and monitoring **iBeacons** and **Eddyston
|
|
|
61
64
|
- [getEventLogs()](#geteventlogsoptions)
|
|
62
65
|
- [clearEventLogs()](#cleareventlogs)
|
|
63
66
|
- [destroyEventLogs()](#destroyeventlogs)
|
|
67
|
+
- [setApiEndpoint()](#setapiendpointurl-apikey-id)
|
|
64
68
|
- [Events](#events)
|
|
65
69
|
- [TypeScript Types](#typescript-types)
|
|
66
70
|
- [Native Integrations](#native-integrations)
|
|
@@ -109,6 +113,8 @@ In Xcode under **Signing & Capabilities**, enable:
|
|
|
109
113
|
- **Background Modes → Location updates**
|
|
110
114
|
- **Background Modes → Uses Bluetooth LE accessories**
|
|
111
115
|
|
|
116
|
+
> When the bundled config plugin is installed (`"plugins": ["expo-beacon"]`), `location` is merged into `UIBackgroundModes` automatically on `expo prebuild` — the native module only enables background ranging when this mode is present.
|
|
117
|
+
|
|
112
118
|
#### Key iOS Constraints
|
|
113
119
|
|
|
114
120
|
- **20 monitored regions max**: iOS limits `CLLocationManager` to 20 simultaneously monitored beacon regions. If you pair more than 20 iBeacons, only the first 20 are monitored. Eddystone beacons use BLE scanning and do **not** count toward this limit.
|
|
@@ -194,6 +200,133 @@ export default function App() {
|
|
|
194
200
|
|
|
195
201
|
---
|
|
196
202
|
|
|
203
|
+
## React Hooks
|
|
204
|
+
|
|
205
|
+
For React / React Native apps the package ships two hooks that wrap the
|
|
206
|
+
imperative API, manage event subscriptions (with automatic cleanup), and expose
|
|
207
|
+
the relevant state reactively. Import them directly from the package:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { useBeacon, useCarPlay } from "expo-beacon";
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Both hooks accept optional event callbacks. Callbacks are read from a ref, so
|
|
214
|
+
passing fresh inline functions on every render does **not** re-subscribe the
|
|
215
|
+
underlying native listeners.
|
|
216
|
+
|
|
217
|
+
### useBeacon()
|
|
218
|
+
|
|
219
|
+
Manages scanning and background monitoring. It keeps the paired-beacon lists,
|
|
220
|
+
the set of beacons currently in range, and the monitoring flag in sync, and
|
|
221
|
+
returns stable action wrappers.
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { useBeacon } from "expo-beacon";
|
|
225
|
+
|
|
226
|
+
function BeaconScreen() {
|
|
227
|
+
const {
|
|
228
|
+
inRange,
|
|
229
|
+
isMonitoring,
|
|
230
|
+
pairedBeacons,
|
|
231
|
+
requestPermissions,
|
|
232
|
+
pairBeacon,
|
|
233
|
+
startMonitoring,
|
|
234
|
+
stopMonitoring,
|
|
235
|
+
} = useBeacon({
|
|
236
|
+
onBeaconEnter: (e) => console.log("entered", e.identifier, e.distance),
|
|
237
|
+
onBeaconExit: (e) => console.log("exited", e.identifier),
|
|
238
|
+
onError: (e) => console.warn(`[${e.code}] ${e.message}`),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<View>
|
|
243
|
+
<Button title="Grant permissions" onPress={requestPermissions} />
|
|
244
|
+
<Button
|
|
245
|
+
title={isMonitoring ? "Stop monitoring" : "Start monitoring"}
|
|
246
|
+
onPress={() => (isMonitoring ? stopMonitoring() : startMonitoring())}
|
|
247
|
+
/>
|
|
248
|
+
{inRange.map((b) => (
|
|
249
|
+
<Text key={b.identifier}>
|
|
250
|
+
{b.identifier} — {b.distance >= 0 ? `${b.distance.toFixed(1)}m` : "n/a"}
|
|
251
|
+
</Text>
|
|
252
|
+
))}
|
|
253
|
+
</View>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Returned value | Description |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| `pairedBeacons` / `pairedEddystones` | Reactive lists of paired devices. |
|
|
261
|
+
| `inRange` | Paired beacons currently in range, derived live from enter/exit/distance/timeout events (`InRangeBeacon[]`). |
|
|
262
|
+
| `isMonitoring` | Whether background monitoring is active. |
|
|
263
|
+
| `isEventLoggingEnabled` | Whether SQLite event logging is enabled (kept in sync by the logging actions). |
|
|
264
|
+
| `refreshPaired()` | Re-read the paired lists from native. |
|
|
265
|
+
| `pairBeacon()` / `unpairBeacon()` | Pair / unpair an iBeacon, then refresh. |
|
|
266
|
+
| `pairEddystone()` / `unpairEddystone()` | Pair / unpair an Eddystone, then refresh. |
|
|
267
|
+
| `scanForBeacons()` / `scanForEddystones()` | One-shot scans returning a promise. |
|
|
268
|
+
| `startContinuousScan()` / `stopContinuousScan()` | Live scan; results arrive via `onBeaconFound` / `onEddystoneFound`. |
|
|
269
|
+
| `cancelScan()` | Cancel an in-progress one-shot scan. |
|
|
270
|
+
| `startMonitoring()` / `stopMonitoring()` | Start / stop background monitoring. |
|
|
271
|
+
| `getMonitoringConfig()` | Read the current monitoring config + active-state snapshot. |
|
|
272
|
+
| `getMonitoredDeviceState()` / `getMonitoredDeviceStates()` | Native state snapshot for one / all paired devices. |
|
|
273
|
+
| `setNotificationConfig()` | Persist notification configuration for monitoring sessions. |
|
|
274
|
+
| `enableEventLogging()` / `disableEventLogging()` | Toggle SQLite logging (updates `isEventLoggingEnabled`). |
|
|
275
|
+
| `getEventLogs()` / `clearEventLogs()` / `destroyEventLogs()` | Read / clear / drop the persisted event log. |
|
|
276
|
+
| `setApiEndpoint()` / `getApiEndpoint()` | Configure / read the native event-forwarding endpoint. |
|
|
277
|
+
| `isBatteryOptimizationExempt()` / `requestBatteryOptimizationExemption()` | Check / request Android battery-optimization exemption. |
|
|
278
|
+
| `requestPermissions()` | Request the permissions needed for scanning / monitoring. |
|
|
279
|
+
|
|
280
|
+
`inRange` reflects **monitored (paired)** beacons only. Continuous-scan results
|
|
281
|
+
are delivered through the `onBeaconFound` / `onEddystoneFound` callbacks because
|
|
282
|
+
raw scan hits carry no paired identifier. Pass `track: false` to skip `inRange`
|
|
283
|
+
bookkeeping when you only need the callbacks.
|
|
284
|
+
|
|
285
|
+
### useCarPlay()
|
|
286
|
+
|
|
287
|
+
Observes CarPlay / Android Auto connection state. It initializes from the
|
|
288
|
+
persisted native state on mount and tracks live connect / disconnect events.
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { useCarPlay } from "expo-beacon";
|
|
292
|
+
|
|
293
|
+
function CarPlayBadge() {
|
|
294
|
+
const { connected, transport, isMonitoring, startMonitoring, stopMonitoring } =
|
|
295
|
+
useCarPlay({
|
|
296
|
+
onConnected: (e) => console.log("car connected via", e.transport),
|
|
297
|
+
onDisconnected: () => console.log("car disconnected"),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<View>
|
|
302
|
+
<Text>{connected ? `Connected (${transport})` : "Not connected"}</Text>
|
|
303
|
+
<Button
|
|
304
|
+
title={isMonitoring ? "Stop" : "Start"}
|
|
305
|
+
onPress={() => (isMonitoring ? stopMonitoring() : startMonitoring())}
|
|
306
|
+
/>
|
|
307
|
+
</View>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Returned value | Description |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `connected` | Whether a CarPlay / Android Auto session is active. |
|
|
315
|
+
| `transport` | Transport of the active session (`CarPlayTransport`), or `null`. |
|
|
316
|
+
| `isMonitoring` | Whether persistent monitoring is enabled. |
|
|
317
|
+
| `lastConnectedAt` / `lastDisconnectedAt` | Epoch-ms timestamps of the last transitions, or `null`. |
|
|
318
|
+
| `startMonitoring()` / `stopMonitoring()` | Enable / disable monitoring. |
|
|
319
|
+
| `refresh()` | Re-read the connection + monitoring state from native. |
|
|
320
|
+
| `getDiagnostics()` | Fetch detection diagnostics for troubleshooting. |
|
|
321
|
+
|
|
322
|
+
Pass `autoStart: true` to call `startCarPlayMonitoring()` on mount when it is
|
|
323
|
+
not already enabled.
|
|
324
|
+
|
|
325
|
+
> Both hooks are safe to call on web: the underlying module is a no-op stub
|
|
326
|
+
> there, so the hooks simply report empty / disconnected state.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
197
330
|
## Usage Examples
|
|
198
331
|
|
|
199
332
|
### Scanning for iBeacons
|
|
@@ -220,7 +353,8 @@ beacons.forEach((b) => {
|
|
|
220
353
|
#### Wildcard scan (Android only)
|
|
221
354
|
|
|
222
355
|
```ts
|
|
223
|
-
// Pass an empty array
|
|
356
|
+
// Pass an empty array (or omit the arguments — defaults are uuids = [],
|
|
357
|
+
// scanDuration = 5000) to discover ALL nearby iBeacons.
|
|
224
358
|
// On iOS, this auto-uses UUIDs from paired beacons
|
|
225
359
|
const beacons = await ExpoBeacon.scanForBeaconsAsync([], 5000);
|
|
226
360
|
```
|
|
@@ -567,7 +701,7 @@ await ExpoBeacon.startMonitoring({
|
|
|
567
701
|
|
|
568
702
|
### Beacon Timeout
|
|
569
703
|
|
|
570
|
-
Pair a beacon with `timeoutSeconds` to fire a one-shot event after the beacon has been
|
|
704
|
+
Pair a beacon with `timeoutSeconds` to fire a one-shot event after the beacon has been out of range for that duration. The countdown is armed when the beacon exits range (or when no BLE readings arrive for 60 seconds, e.g. due to Doze mode or background throttling) and is cancelled if the beacon is seen again before it fires.
|
|
571
705
|
|
|
572
706
|
```tsx
|
|
573
707
|
import { useEffect } from "react";
|
|
@@ -581,7 +715,7 @@ ExpoBeacon.pairBeacon(
|
|
|
581
715
|
1,
|
|
582
716
|
100,
|
|
583
717
|
undefined, // name (optional)
|
|
584
|
-
30, // timeoutSeconds — fires
|
|
718
|
+
30, // timeoutSeconds — fires 30 s after the beacon leaves range
|
|
585
719
|
);
|
|
586
720
|
|
|
587
721
|
// Pair Eddystone with a 60-second timeout
|
|
@@ -590,7 +724,7 @@ ExpoBeacon.pairEddystone(
|
|
|
590
724
|
"edd1ebeac04e5defa017",
|
|
591
725
|
"0123456789ab",
|
|
592
726
|
undefined, // name (optional)
|
|
593
|
-
60, // timeoutSeconds — fires
|
|
727
|
+
60, // timeoutSeconds — fires 60 s after the beacon leaves range
|
|
594
728
|
);
|
|
595
729
|
|
|
596
730
|
// Listen for the timeout events
|
|
@@ -598,13 +732,13 @@ useEffect(() => {
|
|
|
598
732
|
const beaconTimeout = ExpoBeacon.addListener(
|
|
599
733
|
"onBeaconTimeout",
|
|
600
734
|
(e: BeaconTimeoutEvent) => {
|
|
601
|
-
console.log(`Beacon "${e.identifier}"
|
|
735
|
+
console.log(`Beacon "${e.identifier}" out of range for configured duration!`);
|
|
602
736
|
},
|
|
603
737
|
);
|
|
604
738
|
const eddystoneTimeout = ExpoBeacon.addListener(
|
|
605
739
|
"onEddystoneTimeout",
|
|
606
740
|
(e: EddystoneTimeoutEvent) => {
|
|
607
|
-
console.log(`Eddystone "${e.identifier}"
|
|
741
|
+
console.log(`Eddystone "${e.identifier}" out of range for configured duration!`);
|
|
608
742
|
},
|
|
609
743
|
);
|
|
610
744
|
|
|
@@ -615,7 +749,7 @@ useEffect(() => {
|
|
|
615
749
|
}, []);
|
|
616
750
|
```
|
|
617
751
|
|
|
618
|
-
> **Note**: The timeout fires once per
|
|
752
|
+
> **Note**: The timeout fires once per exit. If the beacon re-enters range before the countdown completes, the pending timer is cancelled and re-armed on the next exit.
|
|
619
753
|
|
|
620
754
|
---
|
|
621
755
|
|
|
@@ -774,13 +908,11 @@ If you need to disable this (e.g. you already ship your own Android Auto templat
|
|
|
774
908
|
If `onCarPlayConnected` never fires on Android, call `getCarPlayDiagnostics()` to inspect the native state:
|
|
775
909
|
|
|
776
910
|
```ts
|
|
777
|
-
const d =
|
|
911
|
+
const d = ExpoBeacon.getCarPlayDiagnostics();
|
|
778
912
|
// {
|
|
779
|
-
// platform: "android",
|
|
780
|
-
// carPlayEnabled: true,
|
|
781
913
|
// isCarAppMetadataPresent: true, // false → config plugin didn't run; prebuild again
|
|
782
914
|
// isCarProviderQueryable: true, // false → Android Auto app not installed on device
|
|
783
|
-
// lastRawConnectionType:
|
|
915
|
+
// lastRawConnectionType: 2, // 0=NOT_CONNECTED 1=NATIVE (AAOS) 2=PROJECTION; null = no value yet
|
|
784
916
|
// observerActive: true,
|
|
785
917
|
// serviceAlive: true,
|
|
786
918
|
// }
|
|
@@ -802,8 +934,8 @@ Requests all permissions required for scanning and monitoring.
|
|
|
802
934
|
|
|
803
935
|
| Platform | Permissions Requested |
|
|
804
936
|
|---|---|
|
|
805
|
-
| **Android** | `
|
|
806
|
-
| **iOS** | `CLLocationManager` "When In Use"
|
|
937
|
+
| **Android** | `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`, `BLUETOOTH_SCAN` + `BLUETOOTH_CONNECT` (API 31+), `POST_NOTIFICATIONS` (API 33+), then `ACCESS_BACKGROUND_LOCATION` (API 29+) in a second prompt. Resolves `true` only when background location is granted. |
|
|
938
|
+
| **iOS** | `CLLocationManager` "When In Use" authorization — resolves `true` once granted. The "Always" upgrade is requested later by `startMonitoring()`, and Bluetooth permission is not prompted here. |
|
|
807
939
|
|
|
808
940
|
**Returns**: `true` if all required permissions were granted.
|
|
809
941
|
|
|
@@ -826,6 +958,8 @@ scanForBeaconsAsync(uuids?: string[], scanDurationMs?: number): Promise<BeaconSc
|
|
|
826
958
|
|
|
827
959
|
Performs a **one-shot iBeacon scan**. Waits for the specified duration, then resolves with all discovered beacons.
|
|
828
960
|
|
|
961
|
+
Both parameters are optional — the defaults are applied on the JS side before the native call.
|
|
962
|
+
|
|
829
963
|
| Parameter | Type | Default | Description |
|
|
830
964
|
|---|---|---|---|
|
|
831
965
|
| `uuids` | `string[]` | `[]` | Proximity UUIDs to filter by. See platform differences below. |
|
|
@@ -866,6 +1000,8 @@ scanForEddystonesAsync(scanDurationMs?: number): Promise<EddystoneScanResult[]>
|
|
|
866
1000
|
|
|
867
1001
|
Performs a **one-shot Eddystone scan** using BLE. Discovers both Eddystone-UID and Eddystone-URL frames.
|
|
868
1002
|
|
|
1003
|
+
The parameter is optional — the default is applied on the JS side before the native call.
|
|
1004
|
+
|
|
869
1005
|
| Parameter | Type | Default | Description |
|
|
870
1006
|
|---|---|---|---|
|
|
871
1007
|
| `scanDurationMs` | `number` | `5000` | Scan duration in milliseconds (must be > 0). |
|
|
@@ -932,19 +1068,19 @@ Registers an iBeacon for persistent monitoring.
|
|
|
932
1068
|
|
|
933
1069
|
| Parameter | Type | Description |
|
|
934
1070
|
|---|---|---|
|
|
935
|
-
| `identifier` | `string` | Unique label (e.g. `"lobby-entrance"`). Re-using an identifier replaces the previous entry. |
|
|
1071
|
+
| `identifier` | `string` | Unique label (e.g. `"lobby-entrance"`). Re-using an iBeacon identifier replaces the previous entry. |
|
|
936
1072
|
| `uuid` | `string` | iBeacon proximity UUID (case-insensitive, e.g. `"E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"`) |
|
|
937
1073
|
| `major` | `number` | Major value: `0`–`65535` |
|
|
938
1074
|
| `minor` | `number` | Minor value: `0`–`65535` |
|
|
939
1075
|
| `name` | `string?` | Optional BLE device name for display purposes |
|
|
940
|
-
| `timeoutSeconds` | `number?` | Fire `onBeaconTimeout` once after the beacon
|
|
1076
|
+
| `timeoutSeconds` | `number?` | Fire `onBeaconTimeout` once, this many seconds after the beacon exits range. Cancelled if the beacon is seen again first. |
|
|
941
1077
|
|
|
942
|
-
**Possible errors**: `INVALID_UUID`, `INVALID_MAJOR`, `INVALID_MINOR
|
|
1078
|
+
**Possible errors**: `INVALID_UUID`, `INVALID_MAJOR`, `INVALID_MINOR`, `DUPLICATE_IDENTIFIER` (identifier already used by a paired Eddystone).
|
|
943
1079
|
|
|
944
1080
|
```ts
|
|
945
1081
|
ExpoBeacon.pairBeacon("main-door", "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0", 1, 42);
|
|
946
1082
|
|
|
947
|
-
// With timeout — fires onBeaconTimeout
|
|
1083
|
+
// With timeout — fires onBeaconTimeout 30 s after the beacon leaves range
|
|
948
1084
|
ExpoBeacon.pairBeacon("main-door", "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0", 1, 42, undefined, 30);
|
|
949
1085
|
```
|
|
950
1086
|
|
|
@@ -989,22 +1125,22 @@ const paired = ExpoBeacon.getPairedBeacons();
|
|
|
989
1125
|
pairEddystone(identifier: string, namespace: string, instance: string, name?: string, timeoutSeconds?: number): void
|
|
990
1126
|
```
|
|
991
1127
|
|
|
992
|
-
Registers an Eddystone-UID beacon for persistent monitoring.
|
|
1128
|
+
Registers an Eddystone-UID beacon for persistent monitoring. The namespace and instance are normalized to lowercase before storage.
|
|
993
1129
|
|
|
994
1130
|
| Parameter | Type | Description |
|
|
995
1131
|
|---|---|---|
|
|
996
|
-
| `identifier` | `string` | Unique label (e.g. `"meeting-room"`) |
|
|
1132
|
+
| `identifier` | `string` | Unique label (e.g. `"meeting-room"`). Re-using an Eddystone identifier replaces the previous entry. |
|
|
997
1133
|
| `namespace` | `string` | 10-byte namespace ID as hex string — must be exactly **20 hex characters** |
|
|
998
1134
|
| `instance` | `string` | 6-byte instance ID as hex string — must be exactly **12 hex characters** |
|
|
999
1135
|
| `name` | `string?` | Optional BLE device name for display purposes |
|
|
1000
|
-
| `timeoutSeconds` | `number?` | Fire `onEddystoneTimeout` once after the beacon
|
|
1136
|
+
| `timeoutSeconds` | `number?` | Fire `onEddystoneTimeout` once, this many seconds after the beacon exits range. Cancelled if the beacon is seen again first. |
|
|
1001
1137
|
|
|
1002
|
-
**Possible errors**: `INVALID_NAMESPACE`, `INVALID_INSTANCE
|
|
1138
|
+
**Possible errors**: `INVALID_NAMESPACE`, `INVALID_INSTANCE`, `DUPLICATE_IDENTIFIER` (identifier already used by a paired iBeacon).
|
|
1003
1139
|
|
|
1004
1140
|
```ts
|
|
1005
1141
|
ExpoBeacon.pairEddystone("meeting-room", "edd1ebeac04e5defa017", "0123456789ab");
|
|
1006
1142
|
|
|
1007
|
-
// With timeout — fires onEddystoneTimeout
|
|
1143
|
+
// With timeout — fires onEddystoneTimeout 60 s after the beacon leaves range
|
|
1008
1144
|
ExpoBeacon.pairEddystone("meeting-room", "edd1ebeac04e5defa017", "0123456789ab", undefined, 60);
|
|
1009
1145
|
```
|
|
1010
1146
|
|
|
@@ -1101,7 +1237,7 @@ await ExpoBeacon.startMonitoring();
|
|
|
1101
1237
|
stopMonitoring(): Promise<void>
|
|
1102
1238
|
```
|
|
1103
1239
|
|
|
1104
|
-
Stops all background monitoring. On Android, stops the foreground service.
|
|
1240
|
+
Stops all background monitoring. On Android, stops the foreground service. Persisted monitoring options (`maxDistance`, `exitDistance`, `level`, `exitTimeoutSeconds`, …) are cleared on both platforms.
|
|
1105
1241
|
|
|
1106
1242
|
```ts
|
|
1107
1243
|
await ExpoBeacon.stopMonitoring();
|
|
@@ -1271,6 +1407,24 @@ ExpoBeacon.destroyEventLogs();
|
|
|
1271
1407
|
|
|
1272
1408
|
---
|
|
1273
1409
|
|
|
1410
|
+
### `setApiEndpoint(url, apiKey?, id?)`
|
|
1411
|
+
|
|
1412
|
+
```ts
|
|
1413
|
+
setApiEndpoint(url: string, apiKey?: string, id?: string): void
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
Configures a remote endpoint to which native code POSTs every beacon event — delivery works even when the JS bridge is not active (app backgrounded). The configuration persists until changed.
|
|
1417
|
+
|
|
1418
|
+
| Parameter | Type | Description |
|
|
1419
|
+
|---|---|---|
|
|
1420
|
+
| `url` | `string` | The API endpoint URL to POST events to. |
|
|
1421
|
+
| `apiKey` | `string?` | Sent as the `X-CSFR-Token` header (sic — the header is literally `X-CSFR-Token`, not `X-CSRF-Token`). |
|
|
1422
|
+
| `id` | `string?` | Identifier appended to every forwarded event payload. |
|
|
1423
|
+
|
|
1424
|
+
Use `getApiEndpoint()` to read back the current configuration (each field is `null` if unset).
|
|
1425
|
+
|
|
1426
|
+
---
|
|
1427
|
+
|
|
1274
1428
|
## Events
|
|
1275
1429
|
|
|
1276
1430
|
Subscribe with `ExpoBeacon.addListener(eventName, handler)`. Always call `.remove()` on the returned subscription during cleanup.
|
|
@@ -1293,8 +1447,8 @@ sub.remove();
|
|
|
1293
1447
|
| `onEddystoneEnter` | Paired Eddystone enters range (respects `maxDistance`) | `EddystoneRegionEvent` |
|
|
1294
1448
|
| `onEddystoneExit` | Paired Eddystone leaves range (always fires) | `EddystoneRegionEvent` |
|
|
1295
1449
|
| `onEddystoneDistance` | Periodic Eddystone distance update during monitoring | `EddystoneDistanceEvent` |
|
|
1296
|
-
| `onBeaconTimeout` | Paired iBeacon
|
|
1297
|
-
| `onEddystoneTimeout` | Paired Eddystone
|
|
1450
|
+
| `onBeaconTimeout` | Paired iBeacon out of range for configured `timeoutSeconds` | `BeaconTimeoutEvent` |
|
|
1451
|
+
| `onEddystoneTimeout` | Paired Eddystone out of range for configured `timeoutSeconds` | `EddystoneTimeoutEvent` |
|
|
1298
1452
|
|
|
1299
1453
|
### Event Detail
|
|
1300
1454
|
|
|
@@ -1391,25 +1545,25 @@ ExpoBeacon.addListener("onEddystoneDistance", (e) => {
|
|
|
1391
1545
|
|
|
1392
1546
|
#### `onBeaconTimeout`
|
|
1393
1547
|
|
|
1394
|
-
Fired **once
|
|
1548
|
+
Fired **once**, `timeoutSeconds` after a paired iBeacon exits range (or after BLE readings stop for 60 s). Re-detection cancels the pending timer.
|
|
1395
1549
|
|
|
1396
1550
|
```ts
|
|
1397
1551
|
ExpoBeacon.addListener("onBeaconTimeout", (e) => {
|
|
1398
1552
|
// e.identifier — "lobby-entrance"
|
|
1399
1553
|
// e.uuid, e.major, e.minor — beacon identity
|
|
1400
|
-
// e.distance —
|
|
1401
|
-
console.log(`Beacon "${e.identifier}" timeout —
|
|
1554
|
+
// e.distance — usually –1 (the beacon is out of range when this fires)
|
|
1555
|
+
console.log(`Beacon "${e.identifier}" timeout — out of range for configured duration`);
|
|
1402
1556
|
});
|
|
1403
1557
|
```
|
|
1404
1558
|
|
|
1405
1559
|
#### `onEddystoneTimeout`
|
|
1406
1560
|
|
|
1407
|
-
Fired **once
|
|
1561
|
+
Fired **once**, `timeoutSeconds` after a paired Eddystone exits range (or after BLE readings stop for 60 s). Re-detection cancels the pending timer.
|
|
1408
1562
|
|
|
1409
1563
|
```ts
|
|
1410
1564
|
ExpoBeacon.addListener("onEddystoneTimeout", (e) => {
|
|
1411
1565
|
// e.identifier, e.namespace, e.instance — Eddystone identity
|
|
1412
|
-
// e.distance —
|
|
1566
|
+
// e.distance — usually –1 (the beacon is out of range when this fires)
|
|
1413
1567
|
console.log(`Eddystone "${e.identifier}" timeout`);
|
|
1414
1568
|
});
|
|
1415
1569
|
```
|
|
@@ -1433,6 +1587,7 @@ import type {
|
|
|
1433
1587
|
EddystoneRegionEvent,
|
|
1434
1588
|
EddystoneDistanceEvent,
|
|
1435
1589
|
EddystoneTimeoutEvent,
|
|
1590
|
+
BeaconErrorEvent,
|
|
1436
1591
|
ExpoBeaconModuleEvents,
|
|
1437
1592
|
MonitoringOptions,
|
|
1438
1593
|
NotificationConfig,
|
|
@@ -1454,8 +1609,8 @@ type BeaconScanResult = {
|
|
|
1454
1609
|
major: number; // 0–65535
|
|
1455
1610
|
minor: number; // 0–65535
|
|
1456
1611
|
rssi: number; // Signal strength in dBm (negative, e.g. –65)
|
|
1457
|
-
distance: number; // Estimated distance in metres
|
|
1458
|
-
txPower: number; // Calibrated TX power
|
|
1612
|
+
distance: number; // Estimated distance in metres (–1 when unavailable)
|
|
1613
|
+
txPower: number; // Calibrated TX power. Android only — always 0 on iOS (CoreLocation does not expose it)
|
|
1459
1614
|
};
|
|
1460
1615
|
```
|
|
1461
1616
|
|
|
@@ -1470,7 +1625,7 @@ type PairedBeacon = {
|
|
|
1470
1625
|
major: number;
|
|
1471
1626
|
minor: number;
|
|
1472
1627
|
name?: string; // Optional BLE device name
|
|
1473
|
-
timeoutSeconds?: number; // Fires onBeaconTimeout after
|
|
1628
|
+
timeoutSeconds?: number; // Fires onBeaconTimeout this many seconds after the beacon exits range
|
|
1474
1629
|
};
|
|
1475
1630
|
```
|
|
1476
1631
|
|
|
@@ -1529,7 +1684,7 @@ type PairedEddystone = {
|
|
|
1529
1684
|
namespace: string; // 20 hex chars
|
|
1530
1685
|
instance: string; // 12 hex chars
|
|
1531
1686
|
name?: string; // Optional BLE device name
|
|
1532
|
-
timeoutSeconds?: number; // Fires onEddystoneTimeout after
|
|
1687
|
+
timeoutSeconds?: number; // Fires onEddystoneTimeout this many seconds after the beacon exits range
|
|
1533
1688
|
};
|
|
1534
1689
|
```
|
|
1535
1690
|
|
|
@@ -1670,7 +1825,7 @@ type BeaconTimeoutEvent = {
|
|
|
1670
1825
|
uuid: string;
|
|
1671
1826
|
major: number;
|
|
1672
1827
|
minor: number;
|
|
1673
|
-
distance: number; //
|
|
1828
|
+
distance: number; // Usually –1 (the beacon is out of range when the timeout fires)
|
|
1674
1829
|
};
|
|
1675
1830
|
```
|
|
1676
1831
|
|
|
@@ -1683,7 +1838,7 @@ type EddystoneTimeoutEvent = {
|
|
|
1683
1838
|
identifier: string;
|
|
1684
1839
|
namespace: string;
|
|
1685
1840
|
instance: string;
|
|
1686
|
-
distance: number; //
|
|
1841
|
+
distance: number; // Usually –1 (the beacon is out of range when the timeout fires)
|
|
1687
1842
|
};
|
|
1688
1843
|
```
|
|
1689
1844
|
|
|
@@ -1737,13 +1892,13 @@ Follow [react-native-background-geolocation's native setup](https://transistorso
|
|
|
1737
1892
|
|
|
1738
1893
|
#### 2. Add the Expo config plugin
|
|
1739
1894
|
|
|
1740
|
-
In `app.json` (or `app.config.js`), add `expo-beacon
|
|
1895
|
+
In `app.json` (or `app.config.js`), add `expo-beacon` to your plugins list:
|
|
1741
1896
|
|
|
1742
1897
|
```json
|
|
1743
1898
|
{
|
|
1744
1899
|
"expo": {
|
|
1745
1900
|
"plugins": [
|
|
1746
|
-
"expo-beacon
|
|
1901
|
+
"expo-beacon"
|
|
1747
1902
|
]
|
|
1748
1903
|
}
|
|
1749
1904
|
}
|
|
@@ -1755,7 +1910,28 @@ Then run prebuild to apply the native changes:
|
|
|
1755
1910
|
npx expo prebuild --clean
|
|
1756
1911
|
```
|
|
1757
1912
|
|
|
1758
|
-
The plugin writes `BeaconGeoPlugin.swift` / `BeaconGeoPlugin.kt` into your native project and wires them up in `AppDelegate.swift` and `MainApplication.kt` automatically.
|
|
1913
|
+
The plugin writes `BeaconGeoPlugin.swift` / `BeaconGeoPlugin.kt` into your native project and wires them up in `AppDelegate.swift` and `MainApplication.kt` automatically. It also merges `location` into the iOS `UIBackgroundModes` (required for background ranging) and registers the app as Android-Auto-aware (see [CarPlay / Android Auto Detection](#carplay--android-auto-detection)).
|
|
1914
|
+
|
|
1915
|
+
> **Java projects**: the `MainApplication` patch is Kotlin-only. If your project still uses `MainApplication.java`, the plugin skips the patch and you must add `BeaconPluginRegistry.register(BeaconGeoPlugin(this))` manually.
|
|
1916
|
+
|
|
1917
|
+
##### The `backgroundGeolocation` prop
|
|
1918
|
+
|
|
1919
|
+
The `BeaconGeoPlugin` generation requires `react-native-background-geolocation` to be installed — without it, the generated native files fail to compile. If you don't use that library, disable the integration with the `backgroundGeolocation` prop (default: `true`):
|
|
1920
|
+
|
|
1921
|
+
```json
|
|
1922
|
+
{
|
|
1923
|
+
"expo": {
|
|
1924
|
+
"plugins": [
|
|
1925
|
+
["expo-beacon", {
|
|
1926
|
+
"ios": { "backgroundGeolocation": false },
|
|
1927
|
+
"android": { "backgroundGeolocation": false }
|
|
1928
|
+
}]
|
|
1929
|
+
]
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
Setting it to `false` skips the `BeaconGeoPlugin.swift` / `BeaconGeoPlugin.kt` generation and the `AppDelegate.swift` / `MainApplication.kt` patches. The `UIBackgroundModes` merge and the Android Auto registration are applied regardless of this prop.
|
|
1759
1935
|
|
|
1760
1936
|
#### 3. Configure BGLocation once at JS startup
|
|
1761
1937
|
|
|
@@ -1796,18 +1972,42 @@ import ExpoBeacon
|
|
|
1796
1972
|
import TSLocationManager
|
|
1797
1973
|
|
|
1798
1974
|
final class BeaconGeoPlugin: BeaconLifecycleDelegate {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1975
|
+
private func startTracking() {
|
|
1976
|
+
BackgroundGeolocation.sharedInstance().start()
|
|
1977
|
+
BackgroundGeolocation.sharedInstance().changePace(true)
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
private func stopTracking() {
|
|
1981
|
+
BackgroundGeolocation.sharedInstance().changePace(false)
|
|
1982
|
+
BackgroundGeolocation.sharedInstance().stop()
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
func beaconDidEnter(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
|
|
1986
|
+
startTracking()
|
|
1987
|
+
}
|
|
1988
|
+
func beaconDidExit(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
|
|
1989
|
+
stopTracking()
|
|
1990
|
+
}
|
|
1991
|
+
func beaconDidTimeout(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
|
|
1992
|
+
stopTracking()
|
|
1993
|
+
}
|
|
1994
|
+
func eddystoneDidEnter(identifier: String, namespace: String, instance: String, distance: Double) {
|
|
1995
|
+
startTracking()
|
|
1996
|
+
}
|
|
1997
|
+
func eddystoneDidExit(identifier: String, namespace: String, instance: String, distance: Double) {
|
|
1998
|
+
stopTracking()
|
|
1999
|
+
}
|
|
2000
|
+
func eddystoneDidTimeout(identifier: String, namespace: String, instance: String, distance: Double) {
|
|
2001
|
+
stopTracking()
|
|
2002
|
+
}
|
|
2003
|
+
// Start tracking when the device connects to CarPlay (wired or wireless),
|
|
2004
|
+
// stop when it disconnects.
|
|
2005
|
+
func carPlayDidConnect(transport: String) {
|
|
2006
|
+
startTracking()
|
|
2007
|
+
}
|
|
2008
|
+
func carPlayDidDisconnect() {
|
|
2009
|
+
stopTracking()
|
|
2010
|
+
}
|
|
1811
2011
|
}
|
|
1812
2012
|
```
|
|
1813
2013
|
|
|
@@ -1828,19 +2028,35 @@ package com.yourapp
|
|
|
1828
2028
|
|
|
1829
2029
|
import android.content.Context
|
|
1830
2030
|
import com.transistorsoft.locationmanager.adapter.BackgroundGeolocation
|
|
2031
|
+
import com.transistorsoft.locationmanager.adapter.callback.TSCallback
|
|
1831
2032
|
import expo.modules.beacon.BeaconEventPlugin
|
|
1832
2033
|
|
|
1833
2034
|
class BeaconGeoPlugin(ctx: Context) : BeaconEventPlugin {
|
|
1834
2035
|
private val bgGeo = BackgroundGeolocation.getInstance(ctx, null)
|
|
2036
|
+
private val noOp = object : TSCallback {
|
|
2037
|
+
override fun onSuccess() {}
|
|
2038
|
+
override fun onFailure(error: String) {}
|
|
2039
|
+
}
|
|
1835
2040
|
|
|
1836
2041
|
override fun onBeaconEnter(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) =
|
|
1837
|
-
bgGeo.start(
|
|
2042
|
+
bgGeo.start(noOp)
|
|
1838
2043
|
override fun onBeaconExit(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) =
|
|
1839
|
-
bgGeo.stop(
|
|
2044
|
+
bgGeo.stop(noOp)
|
|
2045
|
+
override fun onBeaconTimeout(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) =
|
|
2046
|
+
bgGeo.stop(noOp)
|
|
1840
2047
|
override fun onEddystoneEnter(identifier: String, namespace: String, instance: String, distance: Double) =
|
|
1841
|
-
bgGeo.start(
|
|
2048
|
+
bgGeo.start(noOp)
|
|
1842
2049
|
override fun onEddystoneExit(identifier: String, namespace: String, instance: String, distance: Double) =
|
|
1843
|
-
bgGeo.stop(
|
|
2050
|
+
bgGeo.stop(noOp)
|
|
2051
|
+
override fun onEddystoneTimeout(identifier: String, namespace: String, instance: String, distance: Double) =
|
|
2052
|
+
bgGeo.stop(noOp)
|
|
2053
|
+
// Start tracking when the device connects to Android Auto, stop when it disconnects.
|
|
2054
|
+
override fun onCarPlayConnected(transport: String) {
|
|
2055
|
+
bgGeo.start(noOp)
|
|
2056
|
+
}
|
|
2057
|
+
override fun onCarPlayDisconnected() {
|
|
2058
|
+
bgGeo.stop(noOp)
|
|
2059
|
+
}
|
|
1844
2060
|
}
|
|
1845
2061
|
```
|
|
1846
2062
|
|
|
@@ -1878,7 +2094,7 @@ override fun onCreate() {
|
|
|
1878
2094
|
|---|---|
|
|
1879
2095
|
| Region monitoring | iOS wakes/relaunches the app on region boundary crossings — even if force-quit. |
|
|
1880
2096
|
| BLE scanning | Eddystones are monitored via CoreBluetooth. Works reliably in foreground; may be throttled when the app is suspended. |
|
|
1881
|
-
| Background modes | `allowsBackgroundLocationUpdates
|
|
2097
|
+
| Background modes | `allowsBackgroundLocationUpdates` is only enabled when `UIBackgroundModes` contains `location` (the config plugin adds it on prebuild); `pausesLocationUpdatesAutomatically = false` |
|
|
1882
2098
|
| Region limit | 20 simultaneous `CLBeaconRegion` registrations max. Eddystones don't count. |
|
|
1883
2099
|
|
|
1884
2100
|
---
|
|
@@ -1915,7 +2131,7 @@ Both the foreground service and enter/exit alerts share the channel ID `expo_bea
|
|
|
1915
2131
|
|
|
1916
2132
|
1. **iBeacon scanning requires UUIDs**: Apple's CoreBluetooth strips iBeacon manufacturer data from BLE advertisements. The module uses `CLLocationManager` ranging with `CLBeaconIdentityConstraint`, which requires known UUIDs. Wildcard iBeacon discovery is architecturally impossible on iOS.
|
|
1917
2133
|
|
|
1918
|
-
2. **Two-step location permission**: iOS requires requesting "When In Use" first, then upgrading to "Always".
|
|
2134
|
+
2. **Two-step location permission**: iOS requires requesting "When In Use" first, then upgrading to "Always". `requestPermissionsAsync()` requests (and resolves `true` with) "When In Use"; the "Always" upgrade prompt is triggered by `startMonitoring()`.
|
|
1919
2135
|
|
|
1920
2136
|
3. **20 region limit**: `CLLocationManager` enforces a hard limit of 20 monitored `CLBeaconRegion` regions across all apps. If your app pairs more than 20 iBeacons, only the first 20 will be actively monitored. Plan your beacon deployment accordingly.
|
|
1921
2137
|
|
|
@@ -1995,6 +2211,7 @@ The module uses hysteresis (3 consecutive readings) to prevent jitter. If you're
|
|
|
1995
2211
|
| `INVALID_MINOR` | `pairBeacon` | Minor value not in range 0–65535. |
|
|
1996
2212
|
| `INVALID_NAMESPACE` | `pairEddystone` | Namespace must be exactly 20 hex characters. |
|
|
1997
2213
|
| `INVALID_INSTANCE` | `pairEddystone` | Instance must be exactly 12 hex characters. |
|
|
2214
|
+
| `DUPLICATE_IDENTIFIER` | `pairBeacon`, `pairEddystone` | The identifier is already used by a paired beacon of the other type. |
|
|
1998
2215
|
| `PERMISSION_DENIED` | `scanForBeaconsAsync`, `startMonitoring` | Required permissions were not granted. |
|
|
1999
2216
|
| `WILDCARD_NOT_SUPPORTED` | `scanForBeaconsAsync` | iOS only: no UUIDs provided and no paired beacons exist. |
|
|
2000
2217
|
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
12
12
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
13
13
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
14
|
-
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
15
|
-
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
16
14
|
|
|
17
15
|
<!-- Location permissions required for BLE scanning -->
|
|
18
16
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|