Skip to content

Commit 2ba9d01

Browse files
authored
Merge pull request #36 from GeoTecINIT/wifi-support
2 parents c689793 + 096037a commit 2ba9d01

File tree

17 files changed

+936
-193
lines changed

17 files changed

+936
-193
lines changed

README.md

Lines changed: 305 additions & 130 deletions
Large diffs are not rendered by default.

demo/app/App_Resources/Android/src/main/AndroidManifest.xml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,26 @@
1010
android:largeScreens="true"
1111
android:xlargeScreens="true"/>
1212

13-
<!-- Always include this permission if your app needs location access -->
14-
<!-- This permission is for "approximate" location data -->
13+
<!-- Always include this permission if your app needs location / Wi-Fi scans access. -->
14+
<!-- This permission is for "approximate" location data and required for Wi-Fi scans -->
1515
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
1616

17-
<!-- Include only if your app benefits from precise location access. -->
18-
<!-- This permission is for "precise" location data -->
17+
<!-- Include only if your app benefits from precise location / Wi-Fi scans access. -->
18+
<!-- This permission is for "precise" location data and required for Wi-Fi scans -->
1919
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2020

21-
<!-- Required only when requesting background location access on
21+
<!-- Required only when requesting background location / Wi-Fi scans access on
2222
Android 10 (API level 29) and higher. -->
2323
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
2424

2525
<!-- The following two permissions are required if your app wants to receive human activity changes -->
2626
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
2727
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
2828

29+
<!-- The following two permissions are required in order to ask and retrieve Wi-Fi scan updates -->
30+
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
31+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
32+
2933
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3034
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3135
<uses-permission android:name="android.permission.INTERNET"/>

demo/app/home/home-page.ts

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ a code-behind file. The code-behind is a great place to place your view
44
logic, and to set up your page’s data binding.
55
*/
66

7-
import { NavigatedData, Page, Application } from "@nativescript/core";
7+
import { Application, NavigatedData, Page } from "@nativescript/core";
88

99
import { HomeViewModel } from "./home-view-model";
1010

@@ -13,47 +13,76 @@ import { Resolution } from "nativescript-context-apis/activity-recognition";
1313

1414
import { GeolocationProvider } from "nativescript-context-apis/geolocation";
1515
import { of, Subscription } from "rxjs";
16+
import {
17+
FingerprintGrouping,
18+
WifiScanProvider,
19+
} from "nativescript-context-apis/internal/wifi";
1620

1721
const activityRecognizers = [Resolution.LOW, Resolution.MEDIUM];
1822

23+
let locationSubscription: Subscription;
24+
let wifiScanSubscription: Subscription;
1925
export function onNavigatingTo(args: NavigatedData) {
2026
const page = <Page>args.object;
2127

2228
page.bindingContext = new HomeViewModel();
2329

2430
let preparing = true;
25-
let locationSubscription: Subscription;
2631
Application.on(Application.resumeEvent, () => {
2732
if (!preparing) {
28-
printCurrentLocation().catch((err) => {
29-
console.error(`Could not print current location: ${err}`);
30-
}).then(() => printLocationUpdates()
31-
.then((subscription) => (locationSubscription = subscription))
32-
.catch(
33-
(err) =>
34-
`An error occurred while getting location updates: ${err}`)
35-
).then(() => listenToActivityChanges());
33+
showUpdates().catch((err) =>
34+
console.error("Could not show updates. Reason: ", err)
35+
);
3636
}
3737
});
3838

3939
Application.on(Application.suspendEvent, () => {
4040
if (!preparing) {
41-
if (locationSubscription) {
42-
locationSubscription.unsubscribe();
43-
}
41+
locationSubscription?.unsubscribe();
42+
wifiScanSubscription?.unsubscribe();
4443
stopListeningToChanges();
4544
}
4645
});
4746

48-
printCurrentLocation().catch((err) => {
49-
console.error(`Could not print current location: ${err}`);
50-
}).then(() => printLocationUpdates()
51-
.then((subscription) => (locationSubscription = subscription))
52-
.catch(
53-
(err) => `An error occurred while getting location updates: ${err}`
54-
)
55-
).then(() => listenToActivityChanges(true))
56-
.then(() => preparing = false);
47+
showUpdates().then(() => (preparing = false));
48+
}
49+
50+
async function showUpdates(addListeners = false): Promise<void> {
51+
const steps: Array<() => Promise<any>> = [
52+
() => listenToActivityChanges(addListeners),
53+
() =>
54+
printCurrentLocation().catch((err) => {
55+
console.error("Could not print current location. Reason:", err);
56+
}),
57+
() =>
58+
printWifiScanResult().catch((err) => {
59+
console.error(
60+
"Could not print current nearby wifi scan. Reason:",
61+
err
62+
);
63+
}),
64+
() =>
65+
printLocationUpdates()
66+
.then((subscription) => (locationSubscription = subscription))
67+
.catch((err) =>
68+
console.error(
69+
"An error occurred while getting location updates. Reason:",
70+
err
71+
)
72+
),
73+
() =>
74+
printWifiScanUpdates()
75+
.then((subscription) => (wifiScanSubscription = subscription))
76+
.catch((err) =>
77+
console.error(
78+
"An error occurred while getting wifi scan updates. Reason:",
79+
err
80+
)
81+
),
82+
];
83+
for (const step of steps) {
84+
await step();
85+
}
5786
}
5887

5988
async function printCurrentLocation() {
@@ -68,6 +97,15 @@ async function printCurrentLocation() {
6897
}
6998
}
7099

100+
async function printWifiScanResult() {
101+
const provider = contextApis.wifiScanProvider;
102+
const ok = await prepareWifiScanProvider(provider);
103+
if (ok) {
104+
const fingerprint = await provider.acquireWifiFingerprint(true);
105+
console.log(`Last wifi scan result: ${JSON.stringify(fingerprint)}`);
106+
}
107+
}
108+
71109
async function printLocationUpdates(): Promise<Subscription> {
72110
const provider = contextApis.geolocationProvider;
73111
const ok = await prepareGeolocationProvider(provider);
@@ -86,7 +124,31 @@ async function printLocationUpdates(): Promise<Subscription> {
86124
next: (location) =>
87125
console.log(`New location acquired!: ${JSON.stringify(location)}`),
88126
error: (error) =>
89-
console.error(`Location updates could not be acquired: ${error}`)
127+
console.error(`Location updates could not be acquired: ${error}`),
128+
});
129+
}
130+
131+
async function printWifiScanUpdates(): Promise<Subscription> {
132+
const provider = contextApis.wifiScanProvider;
133+
const ok = await prepareWifiScanProvider(provider);
134+
135+
await new Promise((resolve) => setTimeout(resolve, 30000));
136+
137+
const stream = ok
138+
? provider.wifiFingerprintStream({
139+
ensureAlwaysNew: true,
140+
grouping: FingerprintGrouping.NONE,
141+
continueOnFailure: true,
142+
})
143+
: of(null);
144+
145+
return stream.subscribe({
146+
next: (fingerprint) =>
147+
console.log(
148+
`New wifi scan result!: ${JSON.stringify(fingerprint)}`
149+
),
150+
error: (error) =>
151+
console.error(`Wifi scan result could not be acquired: ${error}`),
90152
});
91153
}
92154

@@ -96,7 +158,9 @@ export async function listenToActivityChanges(addListener = false) {
96158
await listenToActivityChangesFor(recognizerType, addListener);
97159
} catch (err) {
98160
console.error(
99-
`An error occurred while listening to ${recognizerType} res activity changes: ${JSON.stringify(err)}`
161+
`An error occurred while listening to ${recognizerType} res activity changes: ${JSON.stringify(
162+
err
163+
)}`
100164
);
101165
}
102166
}
@@ -144,7 +208,7 @@ async function listenToActivityChangesFor(
144208
);
145209
}
146210

147-
let _preparing: Promise<any>;
211+
let _preparingGeoProv: Promise<any>;
148212
async function prepareGeolocationProvider(
149213
provider: GeolocationProvider
150214
): Promise<boolean> {
@@ -154,15 +218,42 @@ async function prepareGeolocationProvider(
154218
}
155219

156220
try {
157-
if (!_preparing) {
158-
_preparing = provider.prepare();
221+
if (!_preparingGeoProv) {
222+
_preparingGeoProv = provider.prepare();
159223
}
160-
await _preparing;
224+
await _preparingGeoProv;
161225
return true;
162226
} catch (e) {
163-
console.error(`GeolocationProvider couldn't be prepared: ${JSON.stringify(e)}`);
227+
console.error(
228+
`GeolocationProvider couldn't be prepared: ${JSON.stringify(e)}`
229+
);
230+
return false;
231+
} finally {
232+
_preparingGeoProv = null;
233+
}
234+
}
235+
236+
let _preparingWifiProv: Promise<any>;
237+
async function prepareWifiScanProvider(
238+
provider: WifiScanProvider
239+
): Promise<boolean> {
240+
const isReady = await provider.isReady();
241+
if (isReady) {
242+
return true;
243+
}
244+
245+
try {
246+
if (!_preparingWifiProv) {
247+
_preparingWifiProv = provider.prepare();
248+
}
249+
await _preparingWifiProv;
250+
return true;
251+
} catch (e) {
252+
console.error(
253+
`WifiScanProvider couldn't be prepared: ${JSON.stringify(e)}`
254+
);
164255
return false;
165256
} finally {
166-
_preparing = null;
257+
_preparingWifiProv = null;
167258
}
168259
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package es.uji.geotec.contextapis.wifi;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.util.Log;
7+
8+
public class WifiScanReceiver extends BroadcastReceiver {
9+
private static String tag = "WifiScanReceiver";
10+
11+
private final WifiScanReceiverDelegate delegate;
12+
13+
public WifiScanReceiver(WifiScanReceiverDelegate delegate) {
14+
this.delegate = delegate;
15+
}
16+
17+
@Override
18+
public void onReceive(Context context, Intent intent) {
19+
if (context == null || intent == null) {
20+
return;
21+
}
22+
23+
Log.d(tag, "Wifi scan received!");
24+
delegate.onReceive(context, intent);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package es.uji.geotec.contextapis.wifi;
2+
3+
import es.uji.geotec.contextapis.common.BroadcastReceiverDelegate;
4+
5+
public interface WifiScanReceiverDelegate extends BroadcastReceiverDelegate {
6+
}

src/context-apis.common.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "./activity-recognition";
77

88
import { GeolocationProvider, getGeolocationProvider } from "./geolocation";
9+
import { getWifiScanProvider, WifiScanProvider } from "./wifi";
910

1011
const recognizerTypes = [Resolution.LOW, Resolution.MEDIUM];
1112

@@ -25,4 +26,8 @@ export class Common extends Observable {
2526
get geolocationProvider(): GeolocationProvider {
2627
return getGeolocationProvider();
2728
}
29+
30+
get wifiScanProvider(): WifiScanProvider {
31+
return getWifiScanProvider();
32+
}
2833
}

src/internal/wifi/android/adapter.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { WifiScanAdapter } from "../common";
2+
import { WifiFingerprint } from "../fingerprint";
3+
import { enableLocationRequest, isEnabled } from "@nativescript/geolocation";
4+
import { Utils } from "@nativescript/core";
5+
import { getSeenWifiApsFrom } from "./parsers";
6+
7+
export class AndroidWifiScanAdapter implements WifiScanAdapter {
8+
constructor(
9+
private wifiManager: android.net.wifi.WifiManager = Utils.android
10+
.getApplicationContext()
11+
.getSystemService(android.content.Context.WIFI_SERVICE)
12+
) {}
13+
14+
isReady(): Promise<boolean> {
15+
return isEnabled();
16+
}
17+
18+
prepare(): Promise<void> {
19+
return enableLocationRequest(false, true);
20+
}
21+
22+
async acquireWifiFingerprint(ensureIsNew = true): Promise<WifiFingerprint> {
23+
const ready = await this.isReady();
24+
if (!ready) {
25+
throw new Error(
26+
"Could not acquire wifi fingerprint. Insufficient permissions or disabled location services. Please, call prepare() first before requesting a fingerprint."
27+
);
28+
}
29+
30+
try {
31+
await this.setupScanReceiver();
32+
return this.getLastFingerprint(true);
33+
} catch (err) {
34+
if (ensureIsNew) throw err;
35+
return this.getLastFingerprint(false);
36+
}
37+
}
38+
39+
private setupScanReceiver(): Promise<void> {
40+
return new Promise<void>((resolve, reject) => {
41+
const scanReceiver = new es.uji.geotec.contextapis.wifi.WifiScanReceiver(
42+
new es.uji.geotec.contextapis.wifi.WifiScanReceiverDelegate({
43+
onReceive(
44+
context: android.content.Context,
45+
intent: android.content.Intent
46+
) {
47+
Utils.android
48+
.getApplicationContext()
49+
.unregisterReceiver(scanReceiver);
50+
const success = intent.getBooleanExtra(
51+
android.net.wifi.WifiManager.EXTRA_RESULTS_UPDATED,
52+
false
53+
);
54+
if (success) {
55+
resolve();
56+
} else {
57+
reject(new Error("Results not updated"));
58+
}
59+
},
60+
})
61+
);
62+
const intentFilter = new android.content.IntentFilter();
63+
intentFilter.addAction(
64+
android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
65+
);
66+
Utils.android
67+
.getApplicationContext()
68+
.registerReceiver(scanReceiver, intentFilter);
69+
const success = this.wifiManager.startScan();
70+
if (!success) {
71+
reject(
72+
new Error(
73+
"Could not start scan! Reason: too many requests, device idle or unlikely hardware failure"
74+
)
75+
);
76+
}
77+
});
78+
}
79+
80+
private getLastFingerprint(updateReceived: boolean): WifiFingerprint {
81+
const results = this.wifiManager.getScanResults();
82+
return {
83+
seen: getSeenWifiApsFrom(results),
84+
isNew: updateReceived,
85+
timestamp: new Date(),
86+
};
87+
}
88+
}

0 commit comments

Comments
 (0)