diff --git a/app/src/main/java/org/briarproject/publicmesh/MainActivity.java b/app/src/main/java/org/briarproject/publicmesh/MainActivity.java
index 32256ed0954458f57c4e42c49d5e3097f195c874..38903fd31bef1d81dde4cf18598783dceedf434d 100644
--- a/app/src/main/java/org/briarproject/publicmesh/MainActivity.java
+++ b/app/src/main/java/org/briarproject/publicmesh/MainActivity.java
@@ -59,49 +59,23 @@ public class MainActivity extends AppCompatActivity {
 		((MeshApplication) getApplication()).getAppComponent().inject(this);
 		setContentView(R.layout.activity_main);
 		Button hotspotButton = findViewById(R.id.hotspotButton);
-		Button discoveryButton = findViewById(R.id.discoveryButton);
 		LiveData<Status> hotspotStatus = wifiDirectService.getHotspotStatus();
-		LiveData<Status> discoveryStatus = wifiDirectService.getDiscoveryStatus();
 		hotspotStatus.observe(this, status -> {
 			if (status == STARTING) {
 				hotspotButton.setText(R.string.start_hotspot);
 				hotspotButton.setEnabled(false);
-				discoveryButton.setEnabled(false);
 			} else if (status == STARTED) {
 				hotspotButton.setText(R.string.stop_hotspot);
 				hotspotButton.setEnabled(true);
-				discoveryButton.setEnabled(false);
 			} else if (status == STOPPING) {
 				hotspotButton.setText(R.string.stop_hotspot);
 				hotspotButton.setEnabled(false);
-				discoveryButton.setEnabled(false);
 			} else if (status == STOPPED) {
 				hotspotButton.setText(R.string.start_hotspot);
-				hotspotButton.setEnabled(discoveryStatus.getValue() == STOPPED);
-				discoveryButton.setEnabled(discoveryStatus.getValue() == STOPPED);
-			}
-		});
-		discoveryStatus.observe(this, status -> {
-			if (status == STARTING) {
-				discoveryButton.setText(R.string.start_discovery);
-				discoveryButton.setEnabled(false);
-				hotspotButton.setEnabled(false);
-			} else if (status == STARTED) {
-				discoveryButton.setText(R.string.stop_discovery);
-				discoveryButton.setEnabled(true);
-				hotspotButton.setEnabled(false);
-			} else if (status == STOPPING) {
-				discoveryButton.setText(R.string.stop_discovery);
-				discoveryButton.setEnabled(false);
-				hotspotButton.setEnabled(false);
-			} else if (status == STOPPED) {
-				discoveryButton.setText(R.string.start_discovery);
-				discoveryButton.setEnabled(hotspotStatus.getValue() == STOPPED);
-				hotspotButton.setEnabled(hotspotStatus.getValue() == STOPPED);
+				hotspotButton.setEnabled(true);
 			}
 		});
 		hotspotButton.setOnClickListener(v -> onHotspotButtonClicked());
-		discoveryButton.setOnClickListener(v -> onDiscoveryButtonClicked());
 	}
 
 	@Override
@@ -173,10 +147,4 @@ public class MainActivity extends AppCompatActivity {
 		if (status == STARTED) wifiDirectService.stopHotspot();
 		else if (status == STOPPED) wifiDirectService.startHotspot();
 	}
-
-	private void onDiscoveryButtonClicked() {
-		Status status = wifiDirectService.getDiscoveryStatus().getValue();
-		if (status == STARTED) wifiDirectService.stopDiscovery();
-		else if (status == STOPPED) wifiDirectService.startDiscovery();
-	}
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/briarproject/publicmesh/util/IoUtils.java b/app/src/main/java/org/briarproject/publicmesh/util/IoUtils.java
index 37c0619b5f8dbe821ddc22ada28a16e0362aedf5..169c4b62a7a862ce07586d665b3c0606485d734b 100644
--- a/app/src/main/java/org/briarproject/publicmesh/util/IoUtils.java
+++ b/app/src/main/java/org/briarproject/publicmesh/util/IoUtils.java
@@ -5,18 +5,28 @@ import org.briarproject.publicmesh.nullsafety.NotNullByDefault;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.NetworkInterface;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.annotation.Nullable;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.list;
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
 import static org.briarproject.publicmesh.util.LogUtils.logException;
 
 @NotNullByDefault
 public class IoUtils {
 
+	private static final Logger LOG = getLogger(IoUtils.class.getName());
+
 	public static void tryToClose(@Nullable Socket s, Logger logger,
 			Level level) {
 		try {
@@ -63,4 +73,16 @@ public class IoUtils {
 		ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
 		return ipBytes;
 	}
+
+	public static List<NetworkInterface> getNetworkInterfaces() {
+		try {
+			Enumeration<NetworkInterface> ifaces =
+					NetworkInterface.getNetworkInterfaces();
+			// Despite what the docs say, the return value can be null
+			return ifaces == null ? emptyList() : list(ifaces);
+		} catch (SocketException e) {
+			logException(LOG, WARNING, e);
+			return emptyList();
+		}
+	}
 }
diff --git a/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectService.java b/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectService.java
index 396bc1ecaff37129bb6a1ba060c9a6da2cf1d1ac..9b9319cb276974e39312fee8a400421142478441 100644
--- a/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectService.java
+++ b/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectService.java
@@ -10,12 +10,6 @@ public interface WifiDirectService {
 
 	LiveData<Status> getHotspotStatus();
 
-	void startDiscovery();
-
-	void stopDiscovery();
-
-	LiveData<Status> getDiscoveryStatus();
-
 	enum Status {
 		STARTING,
 		STARTED,
diff --git a/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectServiceImpl.java b/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectServiceImpl.java
index bd3ea5b1bee35f172f9e01eba32e04a8d7a23248..f22a33bab60ac0dba8f0b33a5a33f11b2e1fd2d1 100644
--- a/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectServiceImpl.java
+++ b/app/src/main/java/org/briarproject/publicmesh/wifidirect/WifiDirectServiceImpl.java
@@ -6,57 +6,29 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.DhcpInfo;
-import android.net.MacAddress;
-import android.net.Network;
-import android.net.NetworkRequest;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
-import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
 import android.net.wifi.p2p.WifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager.ActionListener;
 import android.net.wifi.p2p.WifiP2pManager.Channel;
-import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener;
-import android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener;
-import android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener;
 import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
-import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
-import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
 import android.os.Handler;
 
 import org.briarproject.publicmesh.R;
 import org.briarproject.publicmesh.lifecycle.Service;
 import org.briarproject.publicmesh.nullsafety.NotNullByDefault;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.Map;
-import java.util.Random;
-import java.util.TreeMap;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
-import javax.net.SocketFactory;
 
 import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 
-import static android.content.Context.CONNECTIVITY_SERVICE;
 import static android.content.Context.WIFI_P2P_SERVICE;
 import static android.content.Context.WIFI_SERVICE;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
 import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
 import static android.net.wifi.p2p.WifiP2pManager.BUSY;
@@ -66,13 +38,7 @@ import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION;
 import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
 import static android.os.Build.VERSION.SDK_INT;
 import static java.util.Objects.requireNonNull;
-import static java.util.logging.Level.INFO;
 import static java.util.logging.Logger.getLogger;
-import static org.briarproject.publicmesh.util.IoUtils.getInputStream;
-import static org.briarproject.publicmesh.util.IoUtils.getOutputStream;
-import static org.briarproject.publicmesh.util.IoUtils.ipAddressIntToBytes;
-import static org.briarproject.publicmesh.util.IoUtils.tryToClose;
-import static org.briarproject.publicmesh.util.StringUtils.getRandomString;
 import static org.briarproject.publicmesh.wifidirect.WifiDirectService.Status.STARTED;
 import static org.briarproject.publicmesh.wifidirect.WifiDirectService.Status.STARTING;
 import static org.briarproject.publicmesh.wifidirect.WifiDirectService.Status.STOPPED;
@@ -81,15 +47,7 @@ import static org.briarproject.publicmesh.wifidirect.WifiDirectService.Status.ST
 @NotNullByDefault
 class WifiDirectServiceImpl implements WifiDirectService, Service {
 
-	private static final String SERVICE_TYPE = "_mesh._tcp";
-	private static final String KEY_NETWORK_NAME = "ssid";
-	private static final String KEY_PASSPHRASE = "pass";
-	private static final int PORT = 55555;
-
 	private static final int MAX_GROUP_INFO_ATTEMPTS = 5;
-	private static final int MAX_CONNECTION_INFO_ATTEMPTS = 5;
-	private static final int MAX_WIFI_CONNECTION_ATTEMPTS = 5;
-	private static final int MAX_SOCKET_CONNECTION_ATTEMPTS = 5;
 
 	private static final Logger LOG = getLogger(WifiDirectServiceImpl.class.getName());
 
@@ -100,29 +58,13 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 	private final WifiManager wifiManager;
 	@Nullable
 	private final WifiP2pManager wifiP2pManager;
-	private final ConnectivityManager connectivityManager;
 	private final MutableLiveData<Status> hotspotStatus = new MutableLiveData<>(STOPPED);
-	private final MutableLiveData<Status> discoveryStatus = new MutableLiveData<>(STOPPED);
 	private final WifiDirectBroadcastReceiver receiver = new WifiDirectBroadcastReceiver();
-	private final String instanceName = getRandomString(6);
 
 	@Nullable
 	private WifiLock wifiLock;
 	@Nullable
 	private Channel channel;
-	private boolean connectionInProgress = false, connectedToPeer = false;
-	@Nullable
-	private WifiP2pDnsSdServiceInfo serviceInfo;
-	@Nullable
-	private WifiP2pDnsSdServiceRequest serviceRequest;
-	private int networkId = -1;
-	@Nullable
-	private ServerSocket serverSocket;
-	@Nullable
-	private NetworkCallback networkCallback;
-	private SocketFactory socketFactory = SocketFactory.getDefault();
-	@Nullable
-	private SenderThread senderThread;
 
 	@Inject
 	WifiDirectServiceImpl(Application app) {
@@ -131,8 +73,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 		handler = new Handler(app.getMainLooper());
 		wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
 		wifiP2pManager = (WifiP2pManager) app.getSystemService(WIFI_P2P_SERVICE);
-		connectivityManager = (ConnectivityManager)
-				requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
 	}
 
 	@Override
@@ -141,10 +81,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 			LOG.warning("Hotspot already started");
 			return;
 		}
-		if (discoveryStatus.getValue() != STOPPED) {
-			LOG.warning("Service discovery in progress");
-			return;
-		}
 		if (wifiP2pManager == null) {
 			LOG.info("Wifi Direct is not supported");
 			return;
@@ -190,10 +126,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 			LOG.warning("Hotspot is not running");
 			return;
 		}
-		if (discoveryStatus.getValue() != STOPPED) {
-			LOG.warning("Service discovery in progress");
-			return;
-		}
 		hotspotStatus.postValue(STOPPING);
 		requireNonNull(wifiP2pManager).removeGroup(requireNonNull(channel), new ActionListener() {
 
@@ -236,9 +168,10 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 					releaseHotspot();
 				}
 			} else {
+				hotspotStatus.postValue(STARTED);
 				LOG.info("SSID: " + group.getNetworkName());
 				LOG.info("Passphrase: " + group.getPassphrase());
-				addService(group.getNetworkName(), group.getPassphrase());
+				startDiscovery();
 			}
 		};
 		try {
@@ -249,46 +182,7 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 		}
 	}
 
-	private void addService(String networkName, String passphrase) {
-		Channel channel = this.channel;
-		if (channel == null) return;
-		try {
-			serverSocket = new ServerSocket(PORT);
-			LOG.info("Opened server socket " + serverSocket.getLocalSocketAddress());
-			new ServerThread(serverSocket).start();
-		} catch (IOException e) {
-			LOG.info("Failed to open server socket: " + e);
-			releaseHotspot();
-			return;
-		}
-		Map<String, String> record = new TreeMap<>();
-		record.put(KEY_NETWORK_NAME, networkName);
-		record.put(KEY_PASSPHRASE, passphrase);
-		serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(instanceName, SERVICE_TYPE, record);
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				hotspotStatus.postValue(STARTED);
-				LOG.info("Added local service");
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to add local service: " + reason);
-				releaseHotspot();
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).addLocalService(channel, serviceInfo, listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to add local service: " + e);
-			releaseHotspot();
-		}
-	}
-
 	private void releaseHotspot() {
-		closeServerSocket();
-		removeService();
 		if (SDK_INT >= 27) requireNonNull(channel).close();
 		channel = null;
 		releaseLock();
@@ -296,34 +190,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 		LOG.info("Released hotspot");
 	}
 
-	private void closeServerSocket() {
-		if (serverSocket == null) return;
-		tryToClose(serverSocket, LOG, INFO);
-		serverSocket = null;
-	}
-
-	private void removeService() {
-		if (serviceInfo == null) return;
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				LOG.info("Removed local service");
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to remove local service: " + reason);
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).removeLocalService(requireNonNull(channel), serviceInfo,
-					listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to remove local service: " + e);
-		}
-		serviceInfo = null;
-	}
-
 	private void releaseLock() {
 		if (wifiLock != null) {
 			wifiLock.release();
@@ -331,131 +197,34 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 		}
 	}
 
-	@Override
-	public void startDiscovery() {
-		if (discoveryStatus.getValue() != STOPPED) {
-			LOG.warning("Service discovery already started");
-			return;
-		}
-		if (hotspotStatus.getValue() != STOPPED) {
-			LOG.warning("Hotspot in use");
-			return;
-		}
-		if (wifiP2pManager == null) {
-			LOG.info("Wifi Direct is not supported");
-			return;
-		}
-		channel = wifiP2pManager.initialize(app, app.getMainLooper(), null);
-		if (channel == null) {
-			LOG.info("Failed to initialize Wifi Direct");
-			return;
-		}
-		discoveryStatus.postValue(STARTING);
-		acquireLock();
-		DnsSdServiceResponseListener serviceListener = (instanceName, serviceType, device) -> {
-			LOG.info("Discovered service");
-			LOG.info("Instance name: " + instanceName);
-			LOG.info("Service type: " + serviceType);
-			LOG.info("Device name: " + device.deviceName);
-			LOG.info("Device address: " + device.deviceAddress);
-		};
-		DnsSdTxtRecordListener txtListener = (domain, record, device) -> {
-			LOG.info("Discovered TXT record");
-			LOG.info("Domain: " + domain);
-			LOG.info("Record: " + record);
-			LOG.info("Device name: " + device.deviceName);
-			LOG.info("Device address: " + device.deviceAddress);
-			String networkName = record.get(KEY_NETWORK_NAME);
-			String passphrase = record.get(KEY_PASSPHRASE);
-			if (networkName != null && passphrase != null) {
-				connectToPeer(device, networkName, passphrase);
-			}
-		};
-		try {
-			wifiP2pManager.setDnsSdResponseListeners(channel, serviceListener, txtListener);
-			LOG.info("Set DNS SD response listeners");
-			addServiceRequest();
-		} catch (SecurityException e) {
-			LOG.info("Failed to set DNS SD response listeners: " + e);
-			releaseDiscovery();
-		}
-	}
-
-	private void addServiceRequest() {
-		serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				LOG.info("Added service request");
-				discoverServices();
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to add service request: " + reason);
-				releaseDiscovery();
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).addServiceRequest(requireNonNull(channel),
-					serviceRequest, listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to add service request: " + e);
-			releaseDiscovery();
-		}
-	}
-
-	private void discoverServices() {
-		Channel channel = this.channel;
-		if (channel == null) return;
+	private void startDiscovery() {
 		ActionListener listener = new ActionListener() {
 
 			@Override
 			public void onSuccess() {
-				discoveryStatus.postValue(STARTED);
-				LOG.info("Started service discovery");
+				LOG.info("Started peer discovery");
 			}
 
 			@Override
 			public void onFailure(int reason) {
 				if (reason == BUSY) {
-					discoveryStatus.postValue(STARTED);
-					LOG.info("Status BUSY - service discovery already running?");
+					LOG.info("Peer discovery already running?");
 				} else {
-					LOG.info("Failed to start service discovery: " + reason);
-					releaseDiscovery();
+					LOG.info("Failed to start peer discovery: " + reason);
+					releaseHotspot();
 				}
 			}
 		};
 		try {
-			requireNonNull(wifiP2pManager).discoverServices(channel, listener);
+			requireNonNull(wifiP2pManager).discoverPeers(channel, listener);
 		} catch (SecurityException e) {
-			LOG.info("Failed to start service discovery: " + e);
-			releaseDiscovery();
-		}
-	}
-
-	@Override
-	public void stopDiscovery() {
-		if (discoveryStatus.getValue() != STARTED) {
-			LOG.warning("Discovery is not running");
-			return;
-		}
-		if (hotspotStatus.getValue() != STOPPED) {
-			LOG.warning("Hotspot is running");
-			return;
+			LOG.info("Failed to start peer discovery: " + e);
+			releaseHotspot();
 		}
-		discoveryStatus.postValue(STOPPING);
-		releaseDiscovery();
-	}
-
-	@Override
-	public LiveData<Status> getDiscoveryStatus() {
-		return discoveryStatus;
 	}
 
 	private void requestPeers() {
-		if (discoveryStatus.getValue() != STARTED) return;
+		if (hotspotStatus.getValue() != STARTED) return;
 		try {
 			requireNonNull(wifiP2pManager).requestPeers(requireNonNull(channel), devices -> {
 				if (devices == null || devices.getDeviceList() == null) {
@@ -473,245 +242,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 		}
 	}
 
-	private void connectToPeer(WifiP2pDevice device, String networkName, String passphrase) {
-		Channel channel = this.channel;
-		if (channel == null || connectionInProgress || connectedToPeer) return;
-		// If we're using wifi (not WFD) on API 21+ we need the network's socket factory
-		if (SDK_INT >= 21 && SDK_INT < 29) {
-			NetworkRequest networkRequest = new NetworkRequest.Builder()
-					.addTransportType(TRANSPORT_WIFI)
-					.removeCapability(NET_CAPABILITY_INTERNET)
-					.build();
-			networkCallback = new NetworkCallback() {
-				@Override
-				public void onAvailable(Network network) {
-					LOG.info("Network is available, setting socket factory");
-					socketFactory = network.getSocketFactory();
-				}
-			};
-			connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
-		}
-		if (SDK_INT >= 29) {
-			LOG.info("Connecting to peer " + device.deviceName);
-			connectionInProgress = true;
-			WifiP2pConfig config = new WifiP2pConfig.Builder()
-					.setDeviceAddress(MacAddress.fromString(device.deviceAddress))
-					.setNetworkName(networkName)
-					.setPassphrase(passphrase)
-					.build();
-			ActionListener listener = new ActionListener() {
-				@Override
-				public void onSuccess() {
-					LOG.info("Connected to peer " + device.deviceName);
-					connectedToPeer = true;
-					connectionInProgress = false;
-					requestConnectionInfo(1);
-				}
-
-				@Override
-				public void onFailure(int reason) {
-					LOG.info("Failed to connect to peer " + device.deviceName + ": " + reason);
-					connectionInProgress = false;
-				}
-			};
-			try {
-				requireNonNull(wifiP2pManager).connect(channel, config, listener);
-			} catch (SecurityException e) {
-				LOG.info("Failed to connect to peer " + device.deviceName + ": " + e);
-			}
-		} else {
-			WifiConfiguration config = new WifiConfiguration();
-			config.SSID = "\"" + networkName + "\"";
-			config.preSharedKey = "\"" + passphrase + "\"";
-			try {
-				networkId = requireNonNull(wifiManager).addNetwork(config);
-				if (networkId == -1) {
-					LOG.info("Failed to add wifi network");
-				} else {
-					LOG.info("Added wifi network");
-					connectToNetwork(networkName, networkId, 1);
-				}
-			} catch (SecurityException e) {
-				LOG.info("Failed to add wifi network: " + e);
-			}
-		}
-	}
-
-	private void requestConnectionInfo(int attempt) {
-		Channel channel = this.channel;
-		if (channel == null) return;
-		ConnectionInfoListener listener = info -> {
-			if (info.groupOwnerAddress == null) {
-				// On some devices we need to wait for the connection info to become available
-				if (attempt < MAX_CONNECTION_INFO_ATTEMPTS) {
-					handler.postDelayed(() -> requestConnectionInfo(attempt + 1), 1000);
-				} else {
-					LOG.info("Failed to get group owner's address");
-					releaseDiscovery();
-				}
-			} else {
-				LOG.info("Server address " + info.groupOwnerAddress);
-				senderThread = new SenderThread(info.groupOwnerAddress);
-				senderThread.start();
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).requestConnectionInfo(channel, listener);
-			LOG.info("Requested connection info");
-		} catch (SecurityException e) {
-			LOG.info("Failed to request connection info: " + e);
-			releaseDiscovery();
-		}
-	}
-
-	private void connectToNetwork(String networkName, int networkId, int attempt) {
-		String ssid = "\"" + networkName + "\"";
-		try {
-			WifiInfo info = requireNonNull(wifiManager).getConnectionInfo();
-			if (info == null) {
-				LOG.info("Not connected to a wifi network");
-			} else if (!ssid.equals(info.getSSID())) {
-				LOG.info("Connected to unwanted wifi network " + info.getSSID());
-				// If we want to allow the device to remain connected to another network,
-				// we should return here and there's no need to call disconnect() below
-			} else {
-				DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
-				if (dhcpInfo == null) {
-					LOG.info("No DHCP info");
-					// Fall through
-				} else {
-					LOG.info("Successfully connected to wifi network " + networkName);
-					byte[] ipBytes = ipAddressIntToBytes(dhcpInfo.gateway);
-					try {
-						InetAddress serverAddress = InetAddress.getByAddress(ipBytes);
-						LOG.info("Server address " + serverAddress);
-						senderThread = new SenderThread(serverAddress);
-						senderThread.start();
-						return;
-					} catch (UnknownHostException e) {
-						throw new AssertionError(e);
-					}
-				}
-			}
-			if (attempt > MAX_WIFI_CONNECTION_ATTEMPTS) return;
-			LOG.info("Connecting to " + networkName + ", attempt " + attempt);
-			if (wifiManager.disconnect()) LOG.info("Disconnected from wifi network");
-			else LOG.info("Failed to disconnect from wifi network");
-			if (wifiManager.enableNetwork(networkId, true)) LOG.info("Enabled wifi network");
-			else LOG.info("Failed to enable wifi network");
-			if (wifiManager.reconnect()) LOG.info("Reconnected to wifi network");
-			else LOG.info("Failed to reconnect to wifi network");
-			if (wifiManager.reassociate()) LOG.info("Reassociated to wifi network");
-			else LOG.info("Failed to reassociate to wifi network");
-			handler.postDelayed(() -> connectToNetwork(networkName, networkId, attempt + 1), 5000);
-		} catch (SecurityException e) {
-			LOG.info("Failed to connect to wifi network: " + e);
-		}
-	}
-
-	private void releaseDiscovery() {
-		cancelConnection();
-		removeGroup();
-		removeServiceRequest();
-		if (SDK_INT >= 27) requireNonNull(channel).close();
-		channel = null;
-		removeNetwork();
-		unregisterNetworkCallback();
-		interruptSenderThread();
-		releaseLock();
-		discoveryStatus.postValue(STOPPED);
-		LOG.info("Released service discovery");
-	}
-
-	private void cancelConnection() {
-		if (!connectionInProgress) return;
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				LOG.info("Cancelled connection");
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to cancel connection: " + reason);
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).cancelConnect(requireNonNull(channel), listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to cancel connection: " + e);
-		}
-		connectionInProgress = false;
-	}
-
-	private void removeGroup() {
-		if (!connectedToPeer) return;
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				LOG.info("Removed group");
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to remove group: " + reason);
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).removeGroup(requireNonNull(channel), listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to remove group: " + e);
-		}
-		connectedToPeer = false;
-	}
-
-	private void removeServiceRequest() {
-		if (serviceRequest == null) return;
-		ActionListener listener = new ActionListener() {
-			@Override
-			public void onSuccess() {
-				LOG.info("Removed service request");
-			}
-
-			@Override
-			public void onFailure(int reason) {
-				LOG.info("Failed to remove service request: " + reason);
-			}
-		};
-		try {
-			requireNonNull(wifiP2pManager).removeServiceRequest(requireNonNull(channel),
-					serviceRequest, listener);
-		} catch (SecurityException e) {
-			LOG.info("Failed to remove service request: " + e);
-		}
-		serviceRequest = null;
-	}
-
-	private void removeNetwork() {
-		if (networkId == -1) return;
-		try {
-			if (requireNonNull(wifiManager).removeNetwork(networkId)) LOG.info("Removed network");
-			else LOG.info("Failed to remove network");
-		} catch (SecurityException e) {
-			LOG.info("Failed to remove network: " + e);
-		}
-		networkId = -1;
-	}
-
-	private void unregisterNetworkCallback() {
-		if (SDK_INT < 21 || networkCallback == null) return;
-		requireNonNull(connectivityManager).unregisterNetworkCallback(networkCallback);
-		LOG.info("Unregistered network callback");
-		networkCallback = null;
-	}
-
-	private void interruptSenderThread() {
-		if (senderThread == null) return;
-		senderThread.interrupt();
-		LOG.info("Interrupted sender thread");
-		senderThread = null;
-	}
-
 	@Override
 	public void start() {
 		IntentFilter filter = new IntentFilter();
@@ -726,7 +256,6 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 	public void stop() {
 		app.unregisterReceiver(receiver);
 		if (hotspotStatus.getValue() == STARTED) stopHotspot();
-		if (discoveryStatus.getValue() == STARTED) stopDiscovery();
 	}
 
 	private class WifiDirectBroadcastReceiver extends BroadcastReceiver {
@@ -738,114 +267,4 @@ class WifiDirectServiceImpl implements WifiDirectService, Service {
 			if (WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) requestPeers();
 		}
 	}
-
-	private static class ServerThread extends Thread {
-
-		private final ServerSocket serverSocket;
-
-		private ServerThread(ServerSocket serverSocket) {
-			this.serverSocket = serverSocket;
-		}
-
-		@Override
-		public void run() {
-			try {
-				// We'll break out of the loop when the server socket is closed
-				//noinspection InfiniteLoopStatement
-				while (true) {
-					Socket s = serverSocket.accept();
-					LOG.info("Incoming connection from " + s.getRemoteSocketAddress());
-					new ReceiverThread(s).start();
-				}
-			} catch (IOException e) {
-				LOG.info("Server thread exiting: " + e);
-			}
-		}
-	}
-
-	private static class ReceiverThread extends Thread {
-
-		private final Socket socket;
-
-		private ReceiverThread(Socket socket) {
-			this.socket = socket;
-		}
-
-		@Override
-		public void run() {
-			try {
-				InputStream in = getInputStream(socket);
-				OutputStream out = getOutputStream(socket);
-				byte[] buf = new byte[4096];
-				while (true) {
-					int read = in.read(buf);
-					if (read == -1) {
-						LOG.info("Receiver thread exiting: EOF");
-						return;
-					}
-					LOG.info("Received " + read + " bytes");
-					out.write(buf, 0, read);
-					LOG.info("Sent " + read + " bytes");
-				}
-			} catch (IOException e) {
-				LOG.info("Receiver thread exiting: " + e);
-				tryToClose(socket, LOG, INFO);
-			}
-		}
-	}
-
-	private class SenderThread extends Thread {
-
-		private final InetAddress serverAddress;
-
-		private SenderThread(InetAddress serverAddress) {
-			this.serverAddress = serverAddress;
-		}
-
-		@Override
-		public void run() {
-			Socket socket = null;
-			for (int i = 0; i < MAX_SOCKET_CONNECTION_ATTEMPTS; i++) {
-				try {
-					LOG.info("Connecting to " + serverAddress);
-					socket = socketFactory.createSocket(serverAddress, PORT);
-					LOG.info("Connected to " + serverAddress);
-					break;
-				} catch (IOException e) {
-					LOG.info("Failed to connect to " + serverAddress + ": " + e);
-				}
-				try {
-					Thread.sleep(1000);
-				} catch (InterruptedException e) {
-					LOG.info("Sender thread interrupted");
-					return;
-				}
-			}
-			if (socket == null) return;
-			byte[] buf = new byte[4096];
-			Random random = new Random();
-			try {
-				InputStream in = getInputStream(socket);
-				OutputStream out = getOutputStream(socket);
-				// We'll break out of the loop when the sender thread is interrupted
-				while (true) {
-					int bytesToSend =  1 + random.nextInt(99);
-					out.write(buf, 0, bytesToSend);
-					LOG.info("Sent " + bytesToSend + " bytes");
-					int read = in.read(buf);
-					if (read == -1) {
-						LOG.info("Sender thread exiting: EOF");
-						tryToClose(socket, LOG, INFO);
-						return;
-					}
-					LOG.info("Received " + read + " bytes");
-					//noinspection BusyWait
-					Thread.sleep(1000);
-				}
-			} catch (IOException | InterruptedException e) {
-				LOG.info("Sender thread exiting: " + e);
-				tryToClose(socket, LOG, INFO);
-			}
-		}
-	}
 }
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 24bf0c56e53ec99f56b11cf6f5c12b87b140f9ad..88d5a522e663682c3bb94a71ed1faff77d92441a 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -11,20 +11,10 @@
 		android:layout_width="wrap_content"
 		android:layout_height="wrap_content"
 		android:text="@string/start_hotspot"
-		app:layout_constraintVertical_chainStyle="packed"
-		app:layout_constraintBottom_toTopOf="@id/discoveryButton"
-		app:layout_constraintLeft_toLeftOf="parent"
-		app:layout_constraintRight_toRightOf="parent"
-		app:layout_constraintTop_toTopOf="parent" />
-
-	<Button
-		android:id="@+id/discoveryButton"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:text="@string/start_discovery"
 		app:layout_constraintBottom_toBottomOf="parent"
 		app:layout_constraintLeft_toLeftOf="parent"
 		app:layout_constraintRight_toRightOf="parent"
-		app:layout_constraintTop_toBottomOf="@id/hotspotButton" />
+		app:layout_constraintTop_toTopOf="parent"
+		app:layout_constraintVertical_chainStyle="packed" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file