Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wi-Fi RTT Manager Ranging Feature for Android + iOS Fixes to request Location Perms #140

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
</config-file>

<config-file target="res/xml/config.xml" parent="/*">
167 changes: 162 additions & 5 deletions src/android/wifiwizard2/WifiWizard2.java
Original file line number Diff line number Diff line change
@@ -17,13 +17,15 @@
import org.apache.cordova.*;

import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.Future;
import java.lang.InterruptedException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.BroadcastReceiver;
import android.content.Intent;
@@ -36,6 +38,10 @@
import android.net.DhcpInfo;

import android.net.wifi.WifiManager;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.WifiRttManager;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResultCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.ScanResult;
@@ -46,12 +52,13 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.NetworkSpecifier;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.os.Build.VERSION;
import android.os.PatternMatcher;

import androidx.core.app.ActivityCompat;

import java.net.URL;
import java.net.InetAddress;
import java.net.Inet4Address;
@@ -80,6 +87,7 @@ public class WifiWizard2 extends CordovaPlugin {
private static final String IS_WIFI_ENABLED = "isWifiEnabled";
private static final String SET_WIFI_ENABLED = "setWifiEnabled";
private static final String SCAN = "scan";
private static final String SCAN_RTT = "scanWithRTT";
private static final String ENABLE_NETWORK = "enable";
private static final String DISABLE_NETWORK = "disable";
private static final String GET_SSID_NET_ID = "getSSIDNetworkID";
@@ -110,6 +118,7 @@ public class WifiWizard2 extends CordovaPlugin {
private static boolean bssidRequested = false;

private WifiManager wifiManager;
private WifiRttManager rttWifiManager;
private CallbackContext callbackContext;
private JSONArray passedData;

@@ -154,12 +163,13 @@ private static boolean getHexKey(String s) {
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.wifiManager = (WifiManager) cordova.getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
this.rttWifiManager = (WifiRttManager) cordova.getActivity().getApplicationContext().getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
this.connectivityManager = (ConnectivityManager) cordova.getActivity().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}

@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext)
throws JSONException {
throws JSONException {

this.callbackContext = callbackContext;
this.passedData = data;
@@ -196,7 +206,7 @@ public boolean execute(String action, JSONArray data, CallbackContext callbackCo
}

// Return only IP address
if( action.equals( GET_WIFI_IP_ADDRESS ) ){
if (action.equals(GET_WIFI_IP_ADDRESS)) {
callbackContext.success(ip);
return true;
}
@@ -240,6 +250,8 @@ public boolean execute(String action, JSONArray data, CallbackContext callbackCo
this.reconnect(callbackContext);
} else if (action.equals(SCAN)) {
this.scan(callbackContext, data);
} else if (action.equals(SCAN_RTT)) {
this.scanWithRTT(callbackContext, data);
} else if (action.equals(REMOVE_NETWORK)) {
this.remove(callbackContext, data);
} else if (action.equals(CONNECT_NETWORK)) {
@@ -357,6 +369,151 @@ public void run() {
return true;
}

/**
* Scans for RTT Ranging data, must call scan first to get Wifi Scan Results
*
* @param callbackContext A Cordova callback context
* @param data JSONArray with [0] == JSONObject
* @return true
*/
private boolean scanWithRTT(final CallbackContext callbackContext, final JSONArray data) {
Log.v(TAG, "Entering Scan RTT");
final Context context = cordova.getActivity().getApplicationContext();
final ScanSyncContext syncContext = new ScanSyncContext();

synchronized (syncContext) {
if (syncContext.finished) {
Log.v(TAG, "In onReceive, already finished");
return false;
}

syncContext.finished = true;
}

Log.v(TAG, "Checking for RTT support");
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
callbackContext.error("ACCESS_FINE_LOCATION_FALSE");
return false;
}

final List<ScanResult> wifiResults = wifiManager.getScanResults();
JSONArray rttData = new JSONArray();
final String[] error = {""};

if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT) && API_VERSION > 27) {
Log.v(TAG, "RTT is supported");

if (rttWifiManager.isAvailable()) {
Log.v(TAG, "RTT is available");
RangingRequest.Builder builder = new RangingRequest.Builder();

int maxPeers = RangingRequest.getMaxPeers();
int peerCount = 0;

for (ScanResult result : wifiResults) {
if (peerCount >= maxPeers) {
break;
}

if (result.is80211mcResponder()) {
builder.addAccessPoint(result);
peerCount++;
}
}

RangingRequest request = builder.build();

try {
rttWifiManager.startRanging(request, cordova.getThreadPool(), new RangingResultCallback() {
@Override
public void onRangingResults(List<RangingResult> results) {
// Process RTT results
for (RangingResult result : results) {
final int status = result.getStatus();

if (status != STATUS_CODE_FAIL) {
JSONObject rttItem = new JSONObject();

try {
rttItem.put("status", result.getStatus());
rttItem.put("macAddress", result.getMacAddress());
rttItem.put("distanceMm", result.getDistanceMm());
rttItem.put("distanceStdDevMm", result.getDistanceStdDevMm());
rttItem.put("rssi", result.getRssi());
rttData.put(rttItem);
} catch (JSONException e) {
error[0] = e.getMessage();
}
} else {
Log.v(TAG, "Result Failed With Status: " + status);
}
}

try {
JSONObject returnData = new JSONObject();
returnData.put("rttData", rttData);
returnData.put("error", error[0]);

callbackContext.success(returnData);
} catch (JSONException e) {
callbackContext.error("SCAN_WITH_RTT_FAILURE_1");
}
}

@Override
public void onRangingFailure(int code) {
// Handle failure
error[0] = "Ranging failed with code: " + code;
callbackContext.error(error[0]);
}
});
} catch (Exception e) {
error[0] = "SecurityException: " + e.getMessage();
callbackContext.error(error[0]);
}
} else {
Log.v(TAG, "RTT is not available");
error[0] = "RTT_NOT_AVAILABLE";
callbackContext.error("SCAN_WITH_RTT_FAILURE_2");
}
} else {
Log.v(TAG, "RTT is not supported");
error[0] = "RTT_NOT_SUPPORTED";
callbackContext.error("SCAN_WITH_RTT_FAILURE_3");
}

Log.v(TAG, "Submitting timeout to threadpool");
cordova.getThreadPool().submit(new Runnable() {
public void run() {
Log.v(TAG, "Entering timeout");
final int FIFTEEN_SECONDS = 15000;

try {
Thread.sleep(FIFTEEN_SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Received InterruptedException e, " + e);
return;
// keep going into error
}

Log.v(TAG, "Thread sleep done");

synchronized (syncContext) {
if (syncContext.finished) {
Log.v(TAG, "In timeout, already finished");
return;
}
syncContext.finished = true;
}

Log.v(TAG, "In timeout, error");
callbackContext.error("TIMEOUT_WAITING_FOR_RTT");
}
});

return true;
}

/**
* This methods adds a network to the list of available WiFi networks. If the network already
* exists, then it updates it.
@@ -1911,4 +2068,4 @@ private static class AP {
}

}
}
}
3 changes: 2 additions & 1 deletion src/ios/WifiWizard2.h
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
- (void)iOSConnectNetwork:(CDVInvokedUrlCommand *)command;
- (void)iOSConnectOpenNetwork:(CDVInvokedUrlCommand *)command;
- (void)iOSDisconnectNetwork:(CDVInvokedUrlCommand *)command;
- (void)getWifiIP:(CDVInvokedUrlCommand *)command;
- (void)getConnectedSSID:(CDVInvokedUrlCommand *)command;
- (void)getConnectedBSSID:(CDVInvokedUrlCommand *)command;
- (void)isWifiEnabled:(CDVInvokedUrlCommand *)command;
@@ -26,4 +27,4 @@
- (void)canPingWifiRouter:(CDVInvokedUrlCommand *)command;
- (void)canConnectToRouter:(CDVInvokedUrlCommand *)command;

@end
@end
129 changes: 110 additions & 19 deletions src/ios/WifiWizard2.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
#import "WifiWizard2.h"
#include <ifaddrs.h>
#include <arpa/inet.h>
#import <net/if.h>
#import <SystemConfiguration/CaptiveNetwork.h>
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NetworkExtension.h>
#import <CoreLocation/CoreLocation.h>

@implementation WifiWizard2

- (void)getWifiIP:(CDVInvokedUrlCommand*)command {
CDVPluginResult *pluginResult = nil;

NSString *address = @"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];

}

}

temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
if (address) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:address];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Not available"];
}

[self.commandDelegate sendPluginResult:pluginResult
callbackId:command.callbackId];
}

- (id)fetchSSIDInfo {
// see http://stackoverflow.com/a/5198968/907720
NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
@@ -50,30 +90,38 @@ - (void)iOSConnectNetwork:(CDVInvokedUrlCommand*)command {
passwordString = [options objectForKey:@"Password"];

if (@available(iOS 11.0, *)) {
if (ssidString && [ssidString length]) {
NEHotspotConfiguration *configuration = [[NEHotspotConfiguration
alloc] initWithSSID:ssidString
passphrase:passwordString
isWEP:(BOOL)false];

configuration.joinOnce = false;

if (ssidString && [ssidString length]) {
NEHotspotConfiguration *configuration;


if (@available(iOS 13.0, *)) {
configuration = [[NEHotspotConfiguration
alloc] initWithSSIDPrefix:ssidString
passphrase:passwordString
isWEP:(BOOL)false];
} else {
configuration = [[NEHotspotConfiguration
alloc] initWithSSID:ssidString
passphrase:passwordString
isWEP:(BOOL)false];
}
configuration.joinOnce = false;

[[NEHotspotConfigurationManager sharedManager] applyConfiguration:configuration completionHandler:^(NSError * _Nullable error) {

NSDictionary *r = [self fetchSSIDInfo];

NSString *ssid = [r objectForKey:(id)kCNNetworkInfoKeySSID]; //@"SSID"

if ([ssid isEqualToString:ssidString]){
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:ssidString];
if ([ssid hasPrefix:ssidString]){
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:ssid];
}else{
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description];
}
[self.commandDelegate sendPluginResult:pluginResult
callbackId:command.callbackId];
}];


} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"SSID Not provided"];
[self.commandDelegate sendPluginResult:pluginResult
@@ -100,8 +148,16 @@ - (void)iOSConnectOpenNetwork:(CDVInvokedUrlCommand*)command {

if (@available(iOS 11.0, *)) {
if (ssidString && [ssidString length]) {
NEHotspotConfiguration *configuration = [[NEHotspotConfiguration
alloc] initWithSSID:ssidString];
NEHotspotConfiguration *configuration;


if (@available(iOS 13.0, *)) {
configuration = [[NEHotspotConfiguration
alloc] initWithSSIDPrefix:ssidString];
} else {
configuration = [[NEHotspotConfiguration
alloc] initWithSSID:ssidString];
}

configuration.joinOnce = false;

@@ -111,8 +167,8 @@ - (void)iOSConnectOpenNetwork:(CDVInvokedUrlCommand*)command {

NSString *ssid = [r objectForKey:(id)kCNNetworkInfoKeySSID]; //@"SSID"

if ([ssid isEqualToString:ssidString]){
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:ssidString];
if ([ssid hasPrefix:ssidString]){
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:ssid];
}else{
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description];
}
@@ -161,9 +217,8 @@ - (void)iOSDisconnectNetwork:(CDVInvokedUrlCommand*)command {

- (void)getConnectedSSID:(CDVInvokedUrlCommand*)command {
CDVPluginResult *pluginResult = nil;
NSDictionary *r = [self fetchSSIDInfo];

NSString *ssid = [r objectForKey:(id)kCNNetworkInfoKeySSID]; //@"SSID"
NSString *ssid = [self getWifiSsid]; //@"SSID"

if (ssid && [ssid length]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:ssid];
@@ -329,5 +384,41 @@ - (void)canConnectToRouter:(CDVInvokedUrlCommand*)command {
callbackId:command.callbackId];
}

- (NSString*) getWifiSsid {
if (@available(iOS 13.0, *)) {
CLLocationManager* manager = [[CLLocationManager alloc] init];
CLAuthorizationStatus authStatus = [manager authorizationStatus];

if (authStatus == kCLAuthorizationStatusNotDetermined) {
[manager requestWhenInUseAuthorization];
} else if (authStatus == kCLAuthorizationStatusDenied) {
NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings.");
return nil;
}
}

NSString *wifiName = nil;
CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
if (!wifiInterfaces) {
return nil;
}

NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;
for (NSString *interfaceName in interfaces) {
CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));

if (dictRef) {
NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
NSLog(@"network info -> %@", networkInfo);
wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
CFRelease(dictRef);
}
}

CFRelease(wifiInterfaces);
return wifiName;
}



@end
@end
10 changes: 10 additions & 0 deletions www/WifiWizard2.js
Original file line number Diff line number Diff line change
@@ -299,6 +299,16 @@ var WifiWizard2 = {
});
},

/**
* Ranges for Wi-Fi RTT Data if it is supported (must call startScan, just use scan())
* @returns {Promise<any>}
*/
scanWithRTT: function () {
return new Promise(function (resolve, reject) {
cordova.exec(resolve, reject, 'WifiWizard2', 'scanWithRTT', []);
});
},

/**
* Check if WiFi is enabled
* @returns {Promise<any>}