package eu.hgross.blaubot.android.wifi; import android.content.Context; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import eu.hgross.blaubot.core.BlaubotUUIDSet; import eu.hgross.blaubot.core.IBlaubotAdapter; import eu.hgross.blaubot.core.IBlaubotConnection; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionListener; import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener; import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore; import eu.hgross.blaubot.core.connector.IBlaubotConnector; import eu.hgross.blaubot.core.connector.IncompatibleBlaubotDeviceException; import eu.hgross.blaubot.core.statemachine.BlaubotAdapterHelper; import eu.hgross.blaubot.ethernet.BlaubotEthernetConnector; import eu.hgross.blaubot.util.Log; /** * TODO: remove added wifi configs from android os */ public class BlaubotWifiConnector implements IBlaubotConnector { private static final List<String> COMPATIBLE_ACCEPTOR_TYPES = Arrays.asList(WifiConnectionMetaDataDTO.ACCEPTOR_TYPE); private static final String LOG_TAG = "BlaubotWifiConnector"; /** * The timeout for the arp cache (for getting the ip from it) */ private static final long ARP_TIMEOUT = 5000; /** * The min time between arp reads. */ private static final long ARP_INTERVAL = 100; private final BlaubotWifiAdapter adapter; private final IBlaubotDevice ownDevice; private final BlaubotUUIDSet uuidSet; private final WifiManager wifiManager; private final ConnectivityManager connectivityManager; private IBlaubotBeaconStore beaconStore; private IBlaubotIncomingConnectionListener incomingConnectionListener; private Context currentContext; /** * Will contain all used WifiConnector instances and can later be used to remove the added wifi configurations from the android system */ private final Set<WifiConnector> usedWifiConnectorSet; public BlaubotWifiConnector(BlaubotWifiAdapter blaubotWifiAdapter, IBlaubotDevice ownDevice, BlaubotUUIDSet uuidSet, WifiManager wifiManager, ConnectivityManager connectivityManager) { this.usedWifiConnectorSet = Collections.newSetFromMap(new ConcurrentHashMap<WifiConnector, Boolean>()); this.adapter = blaubotWifiAdapter; this.ownDevice = ownDevice; this.uuidSet = uuidSet; this.wifiManager = wifiManager; this.connectivityManager = connectivityManager; } @Override public IBlaubotAdapter getAdapter() { return adapter; } @Override public void setBeaconStore(IBlaubotBeaconStore beaconStore) { this.beaconStore = beaconStore; } @Override public void setIncomingConnectionListener(IBlaubotIncomingConnectionListener acceptorConnectorListener) { this.incomingConnectionListener = acceptorConnectorListener; } /** * Private class that overrides some implementation details of the ethernet conncetor to delegate the * connection heavy lifting. */ private class WifiEthernetConnector extends BlaubotEthernetConnector { public WifiEthernetConnector() { super(adapter, ownDevice); setBeaconStore(beaconStore); } @Override public List<String> getSupportedAcceptorTypes() { final List<String> supportedAcceptorTypes = new ArrayList<>(super.getSupportedAcceptorTypes()); supportedAcceptorTypes.addAll(COMPATIBLE_ACCEPTOR_TYPES); return supportedAcceptorTypes; } } @Override public IBlaubotConnection connectToBlaubotDevice(IBlaubotDevice blaubotDevice) { final String uniqueDeviceID = blaubotDevice.getUniqueDeviceID(); List<ConnectionMetaDataDTO> lastKnownConnectionMetaData = beaconStore.getLastKnownConnectionMetaData(uniqueDeviceID); if(lastKnownConnectionMetaData == null) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Could not get connection meta data for unique device id " + uniqueDeviceID); } return null; } // take the first supported acceptor, if any final List<ConnectionMetaDataDTO> supportedAcceptors = BlaubotAdapterHelper.filterBySupportedAcceptorTypes(lastKnownConnectionMetaData, getSupportedAcceptorTypes()); if(supportedAcceptors.isEmpty()) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "No supported acceptors in meta data to connect to " + uniqueDeviceID + " unfiltered list: " + lastKnownConnectionMetaData); } throw new IncompatibleBlaubotDeviceException(blaubotDevice + " could not get acceptor meta data for this device."); } // take first metadata ConnectionMetaDataDTO dto = supportedAcceptors.get(0); // get connection data // -- the metadata has to be of type wifi WifiConnectionMetaDataDTO wifiConnectionMetaDataDTO = new WifiConnectionMetaDataDTO(dto); final String psk = wifiConnectionMetaDataDTO.getPsk(); final String ssid = wifiConnectionMetaDataDTO.getSsid(); // check if ap mode is on WifiApUtil apUtil = WifiApUtil.createInstance(wifiManager); if(apUtil.isWifiApEnabled()) { apUtil.setWifiApEnabled(apUtil.getWifiApConfiguration(), false); } // now connect to wifi, then to socket final AtomicBoolean wifiConnectResult = new AtomicBoolean(false); final CountDownLatch latch = new CountDownLatch(1); final WifiConnector wc = new WifiConnector(connectivityManager, wifiManager, ssid, psk); Log.d(LOG_TAG, "Connecting to wifi with ssid " + ssid + " and psk " + psk); wc.connect(new WifiConnector.IWifiConnectorCallback() { @Override public void onSuccess() { Log.d(LOG_TAG, "Connected to wifi with ssid " + ssid + " and psk " + psk); wifiConnectResult.set(true); // memorize the connector to remove the config later on usedWifiConnectorSet.add(wc); latch.countDown(); } @Override public void onFailure() { Log.w(LOG_TAG, "Connection to wifi with ssid " + ssid + " and psk " + psk + " failed"); wc.removeAddedWifiConfiguration(); latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); return null; } if(wifiConnectResult.get()) { // try to get the ip from the arp cache (hacky but cool) and modify the meta data accordingly final String acceptorDeviceMacAddr = wifiConnectionMetaDataDTO.getMacAddress(); final String hostIp = getHostIpFromArpCache(acceptorDeviceMacAddr, ARP_TIMEOUT); if(hostIp == null) { Log.d(LOG_TAG, "Failed to get the ip address for mac " + acceptorDeviceMacAddr); wc.removeAddedWifiConfiguration(); return null; } Log.d(LOG_TAG, "Got ip for mac " + acceptorDeviceMacAddr + " from arp cache: " + hostIp); // now modify the meta data wifiConnectionMetaDataDTO.setIpAddress(hostIp); Log.d(LOG_TAG, "Utilizing specialized EthernetConnector to create socket connection"); // now connect to ethernet socket with the specialized EthernetConnector implementation BlaubotEthernetConnector ethernetConnector = new WifiEthernetConnector(); final IBlaubotConnection connection = ethernetConnector.connectToBlaubotDevice(blaubotDevice, wifiConnectionMetaDataDTO); if(connection == null) { wc.removeAddedWifiConfiguration(); } else { // - was successful, add our listener and inform the incoming listener // kill the wifi connection and remove the config on disconnection connection.addConnectionListener(new IBlaubotConnectionListener() { @Override public void onConnectionClosed(IBlaubotConnection connection) { wc.removeAddedWifiConfiguration(); } }); if(incomingConnectionListener != null) { incomingConnectionListener.onConnectionEstablished(connection); } } return connection; } // -- wifi conncet failed Log.w(LOG_TAG, "WifiConnect to " + uniqueDeviceID + " failed."); return null; } @Override public List<String> getSupportedAcceptorTypes() { return COMPATIBLE_ACCEPTOR_TYPES; } /** * Tries to get the ip based on the host's macAddress by scanning the arp-cache. * @param macAddress the host's mac address * @param giveUpAfter the amount of time (ms) after which we give up to get an ip address from arp * @return the corresponding ip address or null, if not possible */ private String getHostIpFromArpCache(String macAddress, long giveUpAfter) { final long start = System.currentTimeMillis(); String ip = null; int i = 0; long delta = -1; while(ip == null) { if(i++ > 0) { try { Thread.sleep(ARP_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); break; } } final long now = System.currentTimeMillis(); delta = now - start; if(delta > giveUpAfter) { break; } ip = WifiUtils.getIpByMACFromARP(macAddress); Log.d(LOG_TAG, "IP from ARP: " + ip); } if(ip != null) { Log.d(LOG_TAG, "Obtained the IP from the arp cache in " + delta + " ms" ); } return ip; } }