package eu.hgross.blaubot.android.wifi; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.util.Log; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Helps to connect to a Wifi Network. * * A {@link WifiConnector} is instantiated with the SSID and a PSK of a Wifi * network. Once you have an instance, you can call either * {@link WifiConnector#connect()} which is blocking for a maximum of 10 * seconds or you utilize * {@link WifiConnector#connect(IWifiConnectorCallback)} to get * asynchronously informed via a listener (the max 10 seconds timeout applies * here too). * * You can also check if you are currently connected to a WiFi network via * {@link WifiConnector#isConnected()}. * * Note that this class is not reusable. Reinstantiate after use! * */ public class WifiConnector { private final WifiApUtil apUtil; public interface IWifiConnectorCallback { public void onSuccess(); public void onFailure(); } private static final long FAIL_TIMEOUT = 8000; // Give up after x milliseconds private static final String LOG_TAG = "WifiConnector"; private String ssid; private String psk; private WifiManager wifiManager; private volatile CountDownLatch connectLatch; private ConnectivityManager connectivityManager; private Integer addedNetworkId; /** * * @param connectivityManager android's connectivity manager service * @param wifiManager android's wifi manager service * @param ssid * the network's SSID * @param psk * the network's pre shared key. If empty string or null, the * network is assumed to be an open network without any * encryption */ public WifiConnector(ConnectivityManager connectivityManager, WifiManager wifiManager, String ssid, String psk) { this.wifiManager = wifiManager; this.connectivityManager = connectivityManager; this.ssid = ssid; this.psk = psk; this.apUtil = WifiApUtil.createInstance(wifiManager); } /** * When using {WifiConnector#connect} a WifiConfiguration is added to the system. * This method removes WifiConfiguration created from the most recent call to connect() * Note that this also disconnects the WiFi if connected. */ public void removeAddedWifiConfiguration() { if(addedNetworkId != null) { Log.d(LOG_TAG, "Removing WiFi network config with id " + addedNetworkId); wifiManager.removeNetwork(addedNetworkId); wifiManager.saveConfiguration(); } } /** * I tried to dynamically register a broadcast receiver to get informed on * this but android (once again) does not act as described in their * documentation. So here you get it ... we poll the state. This buggy * {@link android.content.BroadcastReceiver} system is a pain since version 1.0 ... * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ private abstract class PollNetworkStateThread extends Thread { private static final long POLL_INTERVALL = 150; private CountDownLatch latch; public final AtomicBoolean active = new AtomicBoolean(true); private AtomicBoolean resultBoolean; private Runnable callback; public PollNetworkStateThread(CountDownLatch latch, AtomicBoolean result) { this.latch = latch; this.resultBoolean = result; } public PollNetworkStateThread(CountDownLatch latch, AtomicBoolean result, Runnable finishedCallback) { this(latch, result); this.callback = finishedCallback; } /** * @return true if the condition is meht, false otherwise */ protected abstract boolean pollCheck(); @Override public void run() { while (active.get()) { if (isConnected()) { resultBoolean.set(true); latch.countDown(); break; } try { Thread.sleep(POLL_INTERVALL); } catch (InterruptedException e) { latch.countDown(); break; } } // finished callback if(callback != null) { callback.run(); } } } /** * Checks if the android device is connected to the desired SSID (from * constructor). * * @return true iff connected to the desired SSID */ public boolean isConnected() { if(!wifiManager.isWifiEnabled()) { if(!(apUtil.isApSupported() && apUtil.isWifiApEnabled())) { return false; } } NetworkInfo wifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (wifi == null) { return false; // not supported by device } boolean wifiConnected = wifi.isConnected(); if (!wifiConnected) { return false; } WifiInfo connectionInfo = wifiManager.getConnectionInfo(); String ssid = connectionInfo.getSSID(); boolean isConnected = ssid != null; if (isConnected) { if (ssid.equals("\"" + WifiConnector.this.ssid + "\"")) { return true; } } return false; } /** * Connects to the network (same as connect(boolean) but with async api) * * @param callback the callback to be called when the connect operation finished */ public void connect(final IWifiConnectorCallback callback) { new Thread(new Runnable() { @Override public void run() { boolean succeeded = connect(); if (succeeded) { callback.onSuccess(); } else { callback.onFailure(); } } }).start(); } /** * Activates wifi * @param timeout max time to wait for activation (before fail) * @param finishedCallback called when finished */ private void activateWifi(long timeout, Runnable finishedCallback) { if (wifiManager.isWifiEnabled()) { if(finishedCallback != null) { finishedCallback.run(); } return; } CountDownLatch activatedLatch = new CountDownLatch(1); boolean enabled = wifiManager.setWifiEnabled(true); if (!enabled) { Log.e(LOG_TAG, "Could not enable wifi"); if(finishedCallback != null) { finishedCallback.run(); } return; } final AtomicBoolean result = new AtomicBoolean(false); PollNetworkStateThread poller = new PollNetworkStateThread(activatedLatch, result, finishedCallback) { @Override protected boolean pollCheck() { return wifiManager.isWifiEnabled(); } }; poller.start(); try { boolean timedOut = !activatedLatch.await(timeout, TimeUnit.MILLISECONDS); if(timedOut) { poller.active.set(false); } } catch (InterruptedException e) { Log.e(LOG_TAG, "Got interrupted while waiting"); } } /** * Connects to the network. * * @return true if connection was successfull, false otherwise */ public boolean connect() { final CountDownLatch latch = new CountDownLatch(1); final AtomicBoolean result = new AtomicBoolean(false); activateWifi(FAIL_TIMEOUT, new Runnable() { @Override public void run() { if(wifiManager.isWifiEnabled()) { boolean succeeded = _connect(); result.set(succeeded); } else { result.set(false); } latch.countDown(); } }); try { boolean timedOut = !latch.await(FAIL_TIMEOUT, TimeUnit.MILLISECONDS); if(timedOut) { return false; } } catch (InterruptedException e) { Log.e(LOG_TAG, "Got interrupted while waiting for wifi network connection"); } return result.get(); } private synchronized boolean _connect() { Log.d(LOG_TAG, "Trying to connect to network " + ssid); connectLatch = new CountDownLatch(1); final AtomicBoolean result = new AtomicBoolean(false); final WifiConfiguration wifiConfig = new WifiConfiguration(); wifiConfig.SSID = String.format("\"%s\"", ssid); if (psk == null || psk.isEmpty()) { wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); } else { wifiConfig.preSharedKey = String.format("\"%s\"", psk); } final PollNetworkStateThread connectedToSSIDpoller = new PollNetworkStateThread(connectLatch, result) { @Override protected boolean pollCheck() { return isConnected(); } }; new Thread(new Runnable() { @Override public void run() { Log.d(LOG_TAG, "Adding wifi config"); int netId = wifiManager.addNetwork(wifiConfig); boolean networkAdded = netId != -1; WifiConnector.this.addedNetworkId = netId; // Log.d(LOG_TAG, "Disconnecting from current network (if any)"); // boolean disconnected = wifiManager.disconnect(); boolean disconnected = true; Log.d(LOG_TAG, "Enabling newly added wifi config"); boolean networkEnabled = wifiManager.enableNetwork(netId, true); Log.d(LOG_TAG, "Connecting to network"); boolean reconnected = wifiManager.reconnect(); if (!(networkAdded && disconnected && networkEnabled && reconnected)) { connectLatch.countDown(); result.set(false); return; } connectedToSSIDpoller.start(); } }).start(); try { boolean timeOutOccured = !connectLatch.await(FAIL_TIMEOUT, TimeUnit.MILLISECONDS); if (timeOutOccured) { Log.w(LOG_TAG, "Connection to network " + ssid + " failed (Timeout)"); result.set(false); } } catch (InterruptedException e) { Log.e(LOG_TAG, "Got interrupted while waiting for wifi network connection"); } connectedToSSIDpoller.active.set(false); if (result.get()) { Log.d(LOG_TAG, "Connection to network with SSID " + ssid + " succeeded"); } else { Log.d(LOG_TAG, "Connection to network with SSID " + ssid + " failed"); } return result.get(); } @Override public String toString() { final StringBuffer sb = new StringBuffer("WifiConnector{"); sb.append("ssid='").append(ssid).append('\''); sb.append(", psk='").append(psk).append('\''); sb.append(", addedNetworkId=").append(addedNetworkId); sb.append('}'); return sb.toString(); } }