package org.droidplanner.services.android.impl.utils.connection;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.ScanResult;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import com.o3dr.services.android.lib.gcs.link.LinkConnectionStatus;
import org.droidplanner.services.android.impl.core.MAVLink.connection.MavLinkConnection;
import org.droidplanner.services.android.impl.utils.NetworkUtils;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import timber.log.Timber;
/**
* Used to handle connection with the sololink wifi network.
*/
public class WifiConnectionHandler {
public static final String EXTRA_SSID = "extra_ssid";
public static final String EXTRA_SSID_PASSWORD = "extra_ssid_password";
public static final String EXTRA_SCAN_RESULT = "extra_scan_result";
public interface WifiConnectionListener {
void onWifiConnected(String wifiSsid, Bundle extras);
void onWifiConnecting();
void onWifiDisconnected(String prevConnectedSsid);
void onWifiScanResultsAvailable(List<ScanResult> results);
void onWifiConnectionFailed(LinkConnectionStatus connectionStatus);
}
public static final String SOLO_LINK_WIFI_PREFIX = "SoloLink_";
private static final IntentFilter intentFilter = new IntentFilter();
static {
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
}
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (action) {
case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
notifyWifiScanResultsAvailable(wifiMgr.getScanResults());
break;
case WifiManager.SUPPLICANT_STATE_CHANGED_ACTION:
SupplicantState supState = intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE);
String ssid = NetworkUtils.getCurrentWifiLink(context);
int supplicationError = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
Timber.d("Supplicant state changed error %s with state %s and ssid %s", supplicationError, supState, ssid);
if (supplicationError == WifiManager.ERROR_AUTHENTICATING) {
if (NetworkUtils.isSoloNetwork(ssid)) {
notifyWifiConnectionFailed();
WifiConfiguration wifiConfig = getWifiConfigs(ssid);
if (wifiConfig != null) {
wifiMgr.removeNetwork(wifiConfig.networkId);
}
}
}
break;
case WifiManager.NETWORK_STATE_CHANGED_ACTION:
NetworkInfo netInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
NetworkInfo.State networkState = netInfo == null
? NetworkInfo.State.DISCONNECTED
: netInfo.getState();
switch (networkState) {
case CONNECTED:
final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
final String wifiSSID = wifiInfo.getSSID();
Timber.i("Connected to " + wifiSSID);
final DhcpInfo dhcpInfo = wifiMgr.getDhcpInfo();
if (dhcpInfo != null) {
Timber.i("Dhcp info: %s", dhcpInfo.toString());
} else {
Timber.w("Dhcp info is not available.");
}
if (wifiSSID != null) {
updateNetworkIfNecessary(wifiSSID, null);
}
break;
case DISCONNECTED:
Timber.i("Disconnected from wifi network.");
notifyWifiDisconnected();
break;
case CONNECTING:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
NetworkInfo.DetailedState detailedState = netInfo.getDetailedState();
if (detailedState != null && detailedState == NetworkInfo.DetailedState.VERIFYING_POOR_LINK) {
String connectingSsid = NetworkUtils.getCurrentWifiLink(context);
updateNetworkIfNecessary(connectingSsid, null);
}
}
Timber.d("Connecting to wifi network.");
notifyWifiConnecting();
break;
}
break;
case WifiManager.WIFI_STATE_CHANGED_ACTION:
break;
}
}
};
private final Object netReq;
private final Object netReqCb;
private final AtomicReference<String> connectedWifi = new AtomicReference<>("");
private final Context context;
private final WifiManager wifiMgr;
private final ConnectivityManager connMgr;
private WifiConnectionListener listener;
public WifiConnectionHandler(Context context) {
this.context = context;
this.wifiMgr = (WifiManager) this.context.getSystemService(Context.WIFI_SERVICE);
this.connMgr = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
netReq = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
netReqCb = new ConnectivityManager.NetworkCallback() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void getNetworkInfo(Network network) {
if (network == null) {
Timber.i("Network is null.");
} else {
Timber.i("Network: %s, active : %s", network, connMgr.isDefaultNetworkActive());
LinkProperties linkProps = connMgr.getLinkProperties(network);
Timber.i("Network link properties: %s", linkProps.toString());
Timber.i("Network capabilities: %s", connMgr.getNetworkCapabilities(network));
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onAvailable(Network network) {
//Check if we're still connected to solo. If not, unregister the callbacks
final String currentWifi = getCurrentWifiLink();
if (!isSoloWifi(currentWifi)) {
try {
connMgr.unregisterNetworkCallback(this);
} catch (IllegalArgumentException e) {
Timber.w(e, "Network callback was not registered.");
}
return;
}
Timber.i("Network %s is available", network);
getNetworkInfo(network);
Bundle extras = new Bundle(1);
extras.putParcelable(MavLinkConnection.EXTRA_NETWORK, network);
notifyWifiConnected(currentWifi, extras);
}
@Override
public void onLosing(Network network, int maxMsToLive) {
Timber.w("Losing network %s", network);
}
@Override
public void onLost(Network network) {
Timber.w("Lost network %s", network);
}
};
} else {
netReq = null;
netReqCb = null;
}
}
public void setListener(WifiConnectionListener listener) {
this.listener = listener;
}
/**
* Start the wifi connection handler process.
* It will start listening for wifi connectivity updates, and will handle them as needed.
*/
public void start() {
this.context.registerReceiver(broadcastReceiver, intentFilter);
}
/**
* Stop the wifi connection handler process.
*/
public void stop() {
try {
this.context.unregisterReceiver(broadcastReceiver);
} catch (IllegalArgumentException e) {
Timber.w(e, "Receiver was not registered.");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
connMgr.unregisterNetworkCallback((ConnectivityManager.NetworkCallback) netReqCb);
} catch (IllegalArgumentException e) {
Timber.w(e, "Network callback was not registered.");
}
}
}
/**
* Query available wifi networks
*/
public boolean refreshWifiAPs() {
Timber.d("Querying wifi access points.");
if (wifiMgr == null) {
return false;
}
if (!wifiMgr.isWifiEnabled() && !wifiMgr.setWifiEnabled(true)) {
Toast.makeText(context, "Unable to activate Wi-Fi!", Toast.LENGTH_LONG).show();
return false;
}
return wifiMgr.startScan();
}
public boolean isOnNetwork(String wifiSsid) {
if (TextUtils.isEmpty(wifiSsid)) {
throw new IllegalArgumentException("Invalid wifi ssid " + wifiSsid);
}
return wifiSsid.equalsIgnoreCase(getCurrentWifiLink());
}
public boolean isConnected(String wifiSSID, Bundle info) {
if (!isOnNetwork(wifiSSID)) {
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Network network = info == null
? null
: (Network) info.getParcelable(MavLinkConnection.EXTRA_NETWORK);
if (network == null) {
return false;
}
NetworkCapabilities netCapabilities = connMgr.getNetworkCapabilities(network);
return netCapabilities != null && netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
} else {
return true;
}
}
public List<ScanResult> getScanResults() {
return wifiMgr.getScanResults();
}
private ScanResult getScanResult(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return null;
}
final List<ScanResult> scanResults = wifiMgr.getScanResults();
for (ScanResult result : scanResults) {
if (result.SSID.equalsIgnoreCase(ssid)) {
return result;
}
}
return null;
}
public int connectToWifi(Bundle info) {
if (info == null || info.isEmpty()) {
return LinkConnectionStatus.INVALID_CREDENTIALS;
}
ScanResult scanResult = info.getParcelable(EXTRA_SCAN_RESULT);
if (scanResult == null) {
// Check if the ssid was given.
String ssid = info.getString(EXTRA_SSID);
if (TextUtils.isEmpty(ssid)) {
return LinkConnectionStatus.INVALID_CREDENTIALS;
}
// Get the scan result that match the given ssid.
scanResult = getScanResult(ssid);
if (scanResult == null) {
Timber.i("No matching scan result was found for id %s", ssid);
return LinkConnectionStatus.LINK_UNAVAILABLE;
}
}
Timber.d("Connecting to wifi " + scanResult.SSID);
//Check if we're already connected to the given network.
if (isConnected(scanResult.SSID, info)) {
Timber.d("Already connected to " + scanResult.SSID);
notifyWifiConnected(scanResult.SSID, info);
return 0;
} else if (isOnNetwork(scanResult.SSID)) {
updateNetworkIfNecessary(scanResult.SSID, info);
return 0;
}
WifiConfiguration wifiConfig = getWifiConfigs(scanResult.SSID);
//Network is not configured and needs a password to connect
if (wifiConfig == null) {
String password = info.getString(EXTRA_SSID_PASSWORD);
Timber.d("Connecting to closed wifi network.");
if (TextUtils.isEmpty(password)) {
return LinkConnectionStatus.INVALID_CREDENTIALS;
}
if (!connectToClosedWifi(scanResult, password)) {
return LinkConnectionStatus.UNKNOWN;
}
wifiMgr.saveConfiguration();
wifiConfig = getWifiConfigs(scanResult.SSID);
}
if (wifiConfig != null) {
wifiMgr.enableNetwork(wifiConfig.networkId, true);
return 0;
}
return LinkConnectionStatus.UNKNOWN;
}
private WifiConfiguration getWifiConfigs(String networkSSID) {
List<WifiConfiguration> networks = wifiMgr.getConfiguredNetworks();
if (networks == null) {
return null;
}
for (WifiConfiguration current : networks) {
if (current.SSID != null && current.SSID.equals("\"" + networkSSID + "\"")) {
return current;
}
}
return null;
}
private boolean connectToClosedWifi(ScanResult scanResult, String password) {
final WifiConfiguration wifiConf = new WifiConfiguration();
wifiConf.SSID = "\"" + scanResult.SSID + "\""; //Please note the quotes. String should contain ssid in quotes.
wifiConf.preSharedKey = "\"" + password + "\"";
final int netId = wifiMgr.addNetwork(wifiConf);
if (netId == -1) {
Timber.e("Unable to add wifi configuration for %s", scanResult.SSID);
return false;
}
return true;
}
private static String trimWifiSsid(String wifiSsid) {
if (TextUtils.isEmpty(wifiSsid)) {
return "";
}
return wifiSsid.replace("\"", "");
}
private String getCurrentWifiLink() {
return getCurrentWifiLink(wifiMgr);
}
public static String getCurrentWifiLink(WifiManager wifiMgr) {
final WifiInfo connectedWifi = wifiMgr.getConnectionInfo();
final String connectedSSID = connectedWifi == null ? null : connectedWifi.getSSID();
return trimWifiSsid(connectedSSID);
}
public static boolean isSoloWifi(String wifiSsid) {
return !TextUtils.isEmpty(wifiSsid) && wifiSsid.startsWith(SOLO_LINK_WIFI_PREFIX);
}
private void updateNetworkIfNecessary(String wifiSsid, Bundle info) {
final String trimmedSsid = trimWifiSsid(wifiSsid);
if (isConnected(wifiSsid, info)) {
notifyWifiConnected(wifiSsid, info);
return;
}
if (isSoloWifi(trimmedSsid)) {
//Attempt to connect to the vehicle.
Timber.i("Requesting route to sololink network");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connMgr.requestNetwork((NetworkRequest) netReq, (ConnectivityManager.NetworkCallback) netReqCb);
} else {
notifyWifiConnected(trimmedSsid, info);
}
} else {
notifyWifiConnected(trimmedSsid, info);
}
}
private void notifyWifiConnected(String wifiSsid, Bundle extras) {
if (listener != null) {
listener.onWifiConnected(wifiSsid, extras);
}
}
private void notifyWifiConnecting() {
if (listener != null) {
listener.onWifiConnecting();
}
}
private void notifyWifiDisconnected() {
if (listener != null) {
listener.onWifiDisconnected(connectedWifi.get());
}
connectedWifi.set("");
}
private void notifyWifiScanResultsAvailable(List<ScanResult> results) {
if (listener != null) {
listener.onWifiScanResultsAvailable(results);
}
}
private void notifyWifiConnectionFailed() {
if (listener != null) {
LinkConnectionStatus linkConnectionStatus = LinkConnectionStatus
.newFailedConnectionStatus(LinkConnectionStatus.INVALID_CREDENTIALS, null);
listener.onWifiConnectionFailed(linkConnectionStatus);
}
}
}