From 96b7c35a2a73f878e1abb6fcdea4ab491f92baa4 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Sat, 9 May 2015 13:39:03 -0400 Subject: [PATCH] rework WifiStateChangeService to support hotspots (aka WiFi AP on device) When a device is setup as a WiFi Access Point aka "hotspot", the standard API for getting the WiFi settings returns nothing. We have to use a separate API to get the IP address of the WiFi AP. As far as I could tell, there is no public API for getting the SSID/BSSID of the WiFi AP, so for now that is left blank. That means the wifi screen in swap is confusing because it will say it is not attached when the device is a hotspot @mvdan's https://github.com/mvdan/libaccesspoint should help there #193 https://gitlab.com/fdroid/fdroidclient/issues/193 --- .../fdroid/net/WifiStateChangeService.java | 142 ++++++++++++------ 1 file changed, 94 insertions(+), 48 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index 95a81b7d4..eca334673 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -3,6 +3,7 @@ package org.fdroid.fdroid.net; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.NetworkInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; @@ -17,7 +18,12 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.security.cert.Certificate; +import java.util.Enumeration; import java.util.Locale; public class WifiStateChangeService extends Service { @@ -27,77 +33,67 @@ public class WifiStateChangeService extends Service { private WifiManager wifiManager; private static WaitForWifiAsyncTask asyncTask; + private int wifiState; @Override public int onStartCommand(Intent intent, int flags, int startId) { + FDroidApp.initWifiSettings(); NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); - if (ni == null) { - // this app just started up, NetworkInfo is only passed via WifiStateChangeReceiver - int wifiState = wifiManager.getWifiState(); - if (wifiState == WifiManager.WIFI_STATE_ENABLED) { - startAsyncTask(); + wifiState = wifiManager.getWifiState(); + if (ni == null || ni.isConnected()) { + /* started on app start or from WifiStateChangeReceiver, + NetworkInfo is only passed via WifiStateChangeReceiver */ + Log.i(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState)); + if (wifiState == WifiManager.WIFI_STATE_ENABLED + || wifiState == WifiManager.WIFI_STATE_UNKNOWN) { // might be hotspot + if (asyncTask != null) { + asyncTask.cancel(true); + } + asyncTask = new WaitForWifiAsyncTask(); + asyncTask.execute(); } - } else if (ni.isConnected()) { - Log.i(TAG, "ni.isConnected()"); - startAsyncTask(); - } else { - Log.i("WifiStateChangeReceiver", "ni != null && !ni.isConnected()"); - FDroidApp.initWifiSettings(); } return START_NOT_STICKY; } - private void startAsyncTask() { - Log.i(TAG, "startAsyncTask"); - if (asyncTask != null) { - Log.i(TAG, "asyncTask.cancel"); - asyncTask.cancel(true); - } - asyncTask = new WaitForWifiAsyncTask(); - asyncTask.execute(); - } - public class WaitForWifiAsyncTask extends AsyncTask { private static final String TAG = "WaitForWifiAsyncTask"; @Override protected Void doInBackground(Void... params) { try { - while (!wifiManager.isWifiEnabled()) { - FDroidApp.initWifiSettings(); - if (isCancelled()) + WifiInfo wifiInfo = null; + + wifiState = wifiManager.getWifiState(); + + while (FDroidApp.ipAddressString == null) { + if (isCancelled()) // can be canceled by a change via WifiStateChangeReceiver return null; - if (BuildConfig.DEBUG) { - Log.d(TAG, "waiting for the wifi to be enabled..."); - } + if (wifiState == WifiManager.WIFI_STATE_ENABLED) { + wifiInfo = wifiManager.getConnectionInfo(); + FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress()); + } else + FDroidApp.ipAddressString = getIpAddressFromNetworkInterface(); + // TODO turning off a hotspot leaves wifiState as UNKNOWN with no IP, and this goes until next wifi change Thread.sleep(1000); - } - int ipAddress = wifiManager.getConnectionInfo().getIpAddress(); - while (ipAddress == 0) { - if (isCancelled()) - return null; if (BuildConfig.DEBUG) { Log.d(TAG, "waiting for an IP address..."); } - Thread.sleep(1000); - ipAddress = wifiManager.getConnectionInfo().getIpAddress(); } - if (isCancelled()) + if (isCancelled()) // can be canceled by a change via WifiStateChangeReceiver return null; - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - ipAddress = wifiInfo.getIpAddress(); - FDroidApp.ipAddressString = String.format(Locale.ENGLISH, "%d.%d.%d.%d", - (ipAddress & 0xff), - (ipAddress >> 8 & 0xff), - (ipAddress >> 16 & 0xff), - (ipAddress >> 24 & 0xff)); - final String ssid = wifiInfo.getSSID(); - if (ssid != null) { - FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1"); + if (wifiInfo != null) { + String ssid = wifiInfo.getSSID(); + if (ssid != null) { + FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1"); + } + String bssid = wifiInfo.getBSSID(); + if (bssid != null) { + FDroidApp.bssid = bssid; + } } - FDroidApp.bssid = wifiInfo.getBSSID(); String scheme; if (Preferences.get().isLocalRepoHttpsEnabled()) @@ -108,14 +104,14 @@ public class WifiStateChangeService extends Service { FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo", scheme, FDroidApp.ipAddressString, FDroidApp.port); - if (isCancelled()) + if (isCancelled()) // can be canceled by a change via WifiStateChangeReceiver return null; Context context = WifiStateChangeService.this.getApplicationContext(); LocalRepoManager lrm = LocalRepoManager.get(context); lrm.writeIndexPage(Utils.getSharingUri(FDroidApp.repo).toString()); - if (isCancelled()) + if (isCancelled()) // can be canceled by a change via WifiStateChangeReceiver return null; // the fingerprint for the local repo's signing key @@ -156,4 +152,54 @@ public class WifiStateChangeService extends Service { return null; } + public String getIpAddressFromNetworkInterface() { + try { + for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) { + NetworkInterface netIf = networkInterfaces.nextElement(); + + for (Enumeration inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements(); ) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress.isLoopbackAddress() || inetAddress instanceof Inet6Address) { + continue; + } else if (netIf.getDisplayName().contains("wlan0") + || netIf.getDisplayName().contains("eth0") + || netIf.getDisplayName().contains("ap0")) { + return inetAddress.getHostAddress(); + } + } + } + } catch (SocketException ex) { + ex.printStackTrace(); + } + + return null; + } + + private String formatIpAddress(int ipAddress) { + if (ipAddress == 0) { + return null; + } else { + return String.format(Locale.ENGLISH, "%d.%d.%d.%d", + (ipAddress & 0xff), + (ipAddress >> 8 & 0xff), + (ipAddress >> 16 & 0xff), + (ipAddress >> 24 & 0xff)); + } + } + + private String printWifiState(int wifiState) { + switch (wifiState) { + case WifiManager.WIFI_STATE_DISABLED: + return "WIFI_STATE_DISABLED"; + case WifiManager.WIFI_STATE_DISABLING: + return "WIFI_STATE_DISABLING"; + case WifiManager.WIFI_STATE_ENABLING: + return "WIFI_STATE_ENABLING"; + case WifiManager.WIFI_STATE_ENABLED: + return "WIFI_STATE_ENABLED"; + case WifiManager.WIFI_STATE_UNKNOWN: + return "WIFI_STATE_UNKNOWN"; + } + return null; + } }