diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
index ffb4216ccbbaaf6a9d33fb968106bef131008508..a3a64fa9d3d0f35d58bc5e09e126f5c5c5f5fce8 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
@@ -23,7 +23,6 @@ import java.util.Scanner;
 import javax.annotation.Nullable;
 
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
-import static android.Manifest.permission.BLUETOOTH_SCAN;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Context.MODE_PRIVATE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -54,11 +53,6 @@ public class AndroidUtils {
 		return abis;
 	}
 
-	public static boolean hasBtScanPermission(Context ctx) {
-		return SDK_INT < 31 || ctx.checkPermission(BLUETOOTH_SCAN, myPid(),
-				myUid()) == PERMISSION_GRANTED;
-	}
-
 	public static boolean hasBtConnectPermission(Context ctx) {
 		return SDK_INT < 31 || ctx.checkPermission(BLUETOOTH_CONNECT, myPid(),
 				myUid()) == PERMISSION_GRANTED;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/AbstractBluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/AbstractBluetoothPlugin.java
index 18a9be8947871dc1d9b46570821b7de0e89ceeb4..b9eb6665d7813de83a62e0581ee99b75ad86504c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/AbstractBluetoothPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/AbstractBluetoothPlugin.java
@@ -91,9 +91,10 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
 
 	/**
 	 * Override and return true, if the plugin is now allowed to access the
-	 * Bluetooth hardware, so it must be
+	 * Bluetooth hardware.
+	 * If this returns false, the plugin must be
 	 * {@link org.briarproject.bramble.api.plugin.Plugin.State#DISABLED}
-	 * in {@link #start()}.
+	 * in {@link #start()} and not attempt to access Bluetooth hardware.
 	 */
 	protected boolean isBluetoothAccessible() {
 		return true;
@@ -553,7 +554,8 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
 	private void onSettingsUpdated(Settings settings) {
 		boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
 				DEFAULT_PREF_PLUGIN_ENABLE);
-		SS ss = state.setEnabledByUser(enabledByUser);
+		boolean shouldEnable = enabledByUser && isBluetoothAccessible();
+		SS ss = state.setEnabledByUser(shouldEnable);
 		State s = getState();
 		if (ss != null) {
 			LOG.info("Disabled by user, closing server socket");
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java
index 3ff872c64a07625c131e8cbcc6a3c54b9db5815c..7283086088df2b67fe8b8d8271e85e369b440784 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java
@@ -19,17 +19,17 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION.SDK_INT;
 import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
 import static androidx.core.content.ContextCompat.checkSelfPermission;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
 import static org.briarproject.briar.android.util.Permission.GRANTED;
 import static org.briarproject.briar.android.util.Permission.PERMANENTLY_DENIED;
 import static org.briarproject.briar.android.util.Permission.SHOW_RATIONALE;
 import static org.briarproject.briar.android.util.Permission.UNKNOWN;
-import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
-import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
-import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
-import static org.briarproject.briar.android.util.UiUtils.showRationale;
-import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.areBluetoothPermissionsGranted;
+import static org.briarproject.briar.android.util.PermissionUtils.gotPermission;
+import static org.briarproject.briar.android.util.PermissionUtils.isLocationEnabledForBt;
+import static org.briarproject.briar.android.util.PermissionUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showLocationDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showRationale;
+import static org.briarproject.briar.android.util.PermissionUtils.wasGrantedBluetoothPermissions;
 
 class AddNearbyContactPermissionManager {
 
@@ -64,9 +64,7 @@ class AddNearbyContactPermissionManager {
 		} else if (SDK_INT < 31) {
 			bluetoothOk = checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == ok;
 		} else {
-			bluetoothOk = hasBtConnectPermission(ctx) &&
-					hasBtScanPermission(ctx) &&
-					checkSelfPermission(ctx, BLUETOOTH_ADVERTISE) == ok;
+			bluetoothOk = areBluetoothPermissionsGranted(ctx);
 		}
 		return bluetoothOk && checkSelfPermission(ctx, CAMERA) == ok;
 	}
@@ -79,7 +77,7 @@ class AddNearbyContactPermissionManager {
 	}
 
 	boolean checkPermissions() {
-		boolean locationEnabled = isLocationEnabled(ctx);
+		boolean locationEnabled = isLocationEnabledForBt(ctx);
 		if (locationEnabled && areEssentialPermissionsGranted()) return true;
 		// If an essential permission has been permanently denied, ask the
 		// user to change the setting
@@ -141,7 +139,7 @@ class AddNearbyContactPermissionManager {
 	}
 
 	void onRequestPermissionResult(Map<String, Boolean> result) {
-		if (gotPermission(CAMERA, result)) {
+		if (gotPermission(ctx, result, CAMERA)) {
 			cameraPermission = GRANTED;
 		} else if (shouldShowRationale(CAMERA)) {
 			cameraPermission = SHOW_RATIONALE;
@@ -150,7 +148,7 @@ class AddNearbyContactPermissionManager {
 		}
 		if (isBluetoothSupported) {
 			if (SDK_INT < 31) {
-				if (gotPermission(ACCESS_FINE_LOCATION, result)) {
+				if (gotPermission(ctx, result, ACCESS_FINE_LOCATION)) {
 					locationPermission = GRANTED;
 				} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
 					locationPermission = SHOW_RATIONALE;
@@ -158,7 +156,7 @@ class AddNearbyContactPermissionManager {
 					locationPermission = PERMANENTLY_DENIED;
 				}
 			} else {
-				if (wasGrantedBluetoothPermissions(result)) {
+				if (wasGrantedBluetoothPermissions(ctx, result)) {
 					bluetoothPermissions = GRANTED;
 				} else if (shouldShowRationale(BLUETOOTH_CONNECT)) {
 					bluetoothPermissions = SHOW_RATIONALE;
@@ -169,17 +167,6 @@ class AddNearbyContactPermissionManager {
 		}
 	}
 
-	private boolean gotPermission(String permission,
-			Map<String, Boolean> result) {
-		Boolean permissionResult = result.get(permission);
-		return permissionResult == null ?
-				isGranted(permission) : permissionResult;
-	}
-
-	private boolean isGranted(String permission) {
-		return checkSelfPermission(ctx, permission) == PERMISSION_GRANTED;
-	}
-
 	private boolean shouldShowRationale(String permission) {
 		return shouldShowRequestPermissionRationale(ctx, permission);
 	}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java
index 4920e6da6a7d9e8f71c2fb1c39bdc90ed6194c6b..18351c64c4ff9ec354a9b4f258b3708a91b316ce 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java
@@ -89,8 +89,8 @@ import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContact
 import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER;
 import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
 import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN;
+import static org.briarproject.briar.android.util.PermissionUtils.isLocationEnabledForBt;
 import static org.briarproject.briar.android.util.UiUtils.handleException;
-import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
 
 @NotNullByDefault
 class AddNearbyContactViewModel extends AndroidViewModel
@@ -396,7 +396,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
 	void showQrCodeFragmentIfAllowed() {
 		boolean permissionsGranted = areEssentialPermissionsGranted(
 				getApplication(), isBluetoothSupported());
-		boolean locationEnabled = isLocationEnabled(getApplication());
+		boolean locationEnabled = isLocationEnabledForBt(getApplication());
 		if (wasContinueClicked && permissionsGranted && locationEnabled) {
 			if (isWifiReady() && isBluetoothReady()) {
 				LOG.info("Wifi and Bluetooth are ready");
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/BluetoothConditionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/BluetoothConditionManager.java
index fc80179bc4b62ebc66750d8ac40237709a5f3a91..b5e369c2402f832c11d90dcce2dfcf9d0e0f4d16 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/BluetoothConditionManager.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/BluetoothConditionManager.java
@@ -1,35 +1,34 @@
 package org.briarproject.briar.android.contact.connect;
 
 import android.app.Activity;
-import android.content.Context;
+import android.widget.Toast;
 
 import org.briarproject.briar.R;
 import org.briarproject.briar.android.util.Permission;
-import org.briarproject.briar.android.util.UiUtils;
 
 import java.util.Map;
 
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
-import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.FragmentActivity;
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION.SDK_INT;
+import static android.widget.Toast.LENGTH_LONG;
 import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
-import static androidx.core.content.ContextCompat.checkSelfPermission;
 import static org.briarproject.briar.android.util.Permission.GRANTED;
 import static org.briarproject.briar.android.util.Permission.PERMANENTLY_DENIED;
 import static org.briarproject.briar.android.util.Permission.SHOW_RATIONALE;
 import static org.briarproject.briar.android.util.Permission.UNKNOWN;
-import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
-import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
-import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
-import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
-import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.gotPermission;
+import static org.briarproject.briar.android.util.PermissionUtils.isLocationEnabledForBt;
+import static org.briarproject.briar.android.util.PermissionUtils.requestBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showLocationDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showRationale;
+import static org.briarproject.briar.android.util.PermissionUtils.wasGrantedBluetoothPermissions;
 
 class BluetoothConditionManager {
 
@@ -48,7 +47,7 @@ class BluetoothConditionManager {
 	@UiThread
 	void requestPermissions(ActivityResultLauncher<String[]> launcher) {
 		if (SDK_INT < 31) {
-			launcher.launch(new String[] {ACCESS_FINE_LOCATION});
+			requestLocationPermission(launcher);
 		} else {
 			requestBluetoothPermissions(launcher);
 		}
@@ -58,7 +57,7 @@ class BluetoothConditionManager {
 	void onLocationPermissionResult(Activity activity,
 			@Nullable Map<String, Boolean> result) {
 		if (SDK_INT < 31) {
-			if (gotPermission(activity, result)) {
+			if (gotPermission(activity, result, ACCESS_FINE_LOCATION)) {
 				locationPermission = GRANTED;
 			} else if (shouldShowRequestPermissionRationale(activity,
 					ACCESS_FINE_LOCATION)) {
@@ -67,7 +66,7 @@ class BluetoothConditionManager {
 				locationPermission = PERMANENTLY_DENIED;
 			}
 		} else {
-			if (wasGrantedBluetoothPermissions(result)) {
+			if (wasGrantedBluetoothPermissions(activity, result)) {
 				bluetoothPermissions = GRANTED;
 			} else if (shouldShowRequestPermissionRationale(activity,
 					BLUETOOTH_CONNECT)) {
@@ -84,58 +83,38 @@ class BluetoothConditionManager {
 		boolean permissionGranted =
 				(SDK_INT < 23 || locationPermission == GRANTED) &&
 						bluetoothPermissions == GRANTED;
-		boolean locationEnabled = isLocationEnabled(ctx);
+		boolean locationEnabled = isLocationEnabledForBt(ctx);
 		if (permissionGranted && locationEnabled) return true;
 
 		if (locationPermission == PERMANENTLY_DENIED) {
-			showDenialDialog(ctx, onLocationDenied);
+			showDenialDialog(ctx, R.string.permission_location_title,
+					R.string.permission_location_denied_body, onLocationDenied);
 		} else if (locationPermission == SHOW_RATIONALE) {
-			showRationale(ctx, permissionRequest);
+			showRationale(ctx, R.string.permission_location_title,
+					R.string.permission_location_request_body,
+					() -> requestLocationPermission(permissionRequest));
 		} else if (!locationEnabled) {
 			showLocationDialog(ctx);
 		} else if (bluetoothPermissions == PERMANENTLY_DENIED) {
-			UiUtils.showDenialDialog(ctx, R.string.permission_bluetooth_title,
-					R.string.permission_bluetooth_denied_body);
+			Runnable onDenied = () -> Toast.makeText(ctx,
+					R.string.connect_via_bluetooth_no_bluetooth_permission,
+					LENGTH_LONG).show();
+			showDenialDialog(ctx, R.string.permission_bluetooth_title,
+					R.string.permission_bluetooth_denied_body, onDenied);
 		} else if (bluetoothPermissions == SHOW_RATIONALE && SDK_INT >= 31) {
-			UiUtils.showRationale(ctx, R.string.permission_bluetooth_title,
+			// SDK_INT is checked to make linter happy, because
+			// requestBluetoothPermissions() requires SDK_INT 31
+			showRationale(ctx,
+					R.string.permission_bluetooth_title,
 					R.string.permission_bluetooth_body, () ->
 							requestBluetoothPermissions(permissionRequest));
 		}
 		return false;
 	}
 
-	private void showDenialDialog(Context ctx, Runnable onLocationDenied) {
-		new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
-				.setTitle(R.string.permission_location_title)
-				.setMessage(R.string.permission_location_denied_body)
-				.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
-				.setNegativeButton(R.string.cancel, (v, d) ->
-						onLocationDenied.run())
-				.show();
-	}
-
-	private void showRationale(Context ctx,
-			ActivityResultLauncher<String[]> permissionRequest) {
-		new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
-				.setTitle(R.string.permission_location_title)
-				.setMessage(R.string.permission_location_request_body)
-				.setPositiveButton(R.string.ok, (dialog, which) ->
-						permissionRequest.launch(
-								new String[] {ACCESS_FINE_LOCATION}))
-				.show();
-	}
-
-	private boolean gotPermission(Context ctx,
-			@Nullable Map<String, Boolean> result) {
-		Boolean permissionResult =
-				result == null ? null : result.get(ACCESS_FINE_LOCATION);
-		return permissionResult == null ? isLocationPermissionGranted(ctx) :
-				permissionResult;
-	}
-
-	private boolean isLocationPermissionGranted(Context ctx) {
-		return checkSelfPermission(ctx, ACCESS_FINE_LOCATION) ==
-				PERMISSION_GRANTED;
+	private void requestLocationPermission(
+			ActivityResultLauncher<String[]> launcher) {
+		launcher.launch(new String[] {ACCESS_FINE_LOCATION});
 	}
 
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/ConnectViaBluetoothViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/ConnectViaBluetoothViewModel.java
index 3a1cf0f754e2e5aa3a9406317116398118c81996..3f6a6c066d78d7ee836cbc307c12e34029703ccc 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/ConnectViaBluetoothViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/connect/ConnectViaBluetoothViewModel.java
@@ -46,6 +46,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
 import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
 import static org.briarproject.bramble.util.LogUtils.logException;
 import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
+import static org.briarproject.briar.android.util.PermissionUtils.areBluetoothPermissionsGranted;
 
 @UiThread
 @NotNullByDefault
@@ -260,7 +261,8 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
 
 	private void stopConnecting() {
 		BluetoothPlugin bluetoothPlugin = this.bluetoothPlugin;
-		if (bluetoothPlugin != null) {
+		if (bluetoothPlugin != null &&
+				areBluetoothPermissionsGranted(getApplication())) {
 			bluetoothPlugin.stopDiscoverAndConnect();
 		}
 	}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java
index d0e65a93f6b6a705ca220f95c69a4f790ba92c4a..edfef730342426784d2cc0fbbe9fa9c0da7c02bf 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java
@@ -1,7 +1,6 @@
 package org.briarproject.briar.android.hotspot;
 
 import android.content.Context;
-import android.content.DialogInterface;
 import android.net.wifi.WifiManager;
 
 import org.briarproject.briar.R;
@@ -20,6 +19,12 @@ import static android.content.Context.WIFI_SERVICE;
  */
 abstract class AbstractConditionManager {
 
+	/**
+	 * Consumes false, if permissions have been denied. Then we don't call
+	 * {@link HotspotIntroFragment#startHotspotIfConditionsFulfilled()},
+	 * which would result in the same permission being requested again
+	 * immediately.
+	 */
 	final Consumer<Boolean> permissionUpdateCallback;
 	protected FragmentActivity ctx;
 	WifiManager wifiManager;
@@ -52,19 +57,6 @@ abstract class AbstractConditionManager {
 	 */
 	abstract boolean checkAndRequestConditions();
 
-	void showDenialDialog(FragmentActivity ctx,
-			@StringRes int title, @StringRes int body,
-			DialogInterface.OnClickListener onOkClicked, Runnable onDismiss) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
-		builder.setTitle(title);
-		builder.setMessage(body);
-		builder.setPositiveButton(R.string.ok, onOkClicked);
-		builder.setNegativeButton(R.string.cancel,
-				(dialog, which) -> ctx.supportFinishAfterTransition());
-		builder.setOnDismissListener(dialog -> onDismiss.run());
-		builder.show();
-	}
-
 	void showRationale(Context ctx, @StringRes int title,
 			@StringRes int body, Runnable onContinueClicked,
 			Runnable onDismiss) {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java
index 866f9cc917e4fa1724c7b70e5731d710560d7ec9..64e39960573b45915671c23f0b2c8bb5396282f5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java
@@ -1,11 +1,11 @@
 package org.briarproject.briar.android.hotspot;
 
 import android.content.Intent;
-import android.location.LocationManager;
 import android.provider.Settings;
 
 import org.briarproject.briar.R;
 import org.briarproject.briar.android.util.Permission;
+import org.briarproject.briar.android.util.PermissionUtils;
 
 import java.util.logging.Logger;
 
@@ -23,8 +23,8 @@ import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRation
 import static java.lang.Boolean.TRUE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Logger.getLogger;
-import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
-import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.isLocationEnabledForWiFi;
+import static org.briarproject.briar.android.util.PermissionUtils.showLocationDialog;
 
 /**
  * This class ensures that the conditions to open a hotspot are fulfilled on
@@ -47,6 +47,7 @@ class ConditionManager29 extends AbstractConditionManager {
 	ConditionManager29(ActivityResultCaller arc,
 			Consumer<Boolean> permissionUpdateCallback) {
 		super(permissionUpdateCallback);
+		// permissionUpdateCallback receives false if permissions were denied
 		locationRequest = arc.registerForActivityResult(
 				new RequestPermission(), granted -> {
 					onRequestPermissionResult(granted);
@@ -66,7 +67,7 @@ class ConditionManager29 extends AbstractConditionManager {
 
 	private boolean areEssentialPermissionsGranted() {
 		boolean isWifiEnabled = wifiManager.isWifiEnabled();
-		boolean isLocationEnabled = isLocationEnabled();
+		boolean isLocationEnabled = isLocationEnabledForWiFi(ctx);
 		if (LOG.isLoggable(INFO)) {
 			LOG.info(String.format("areEssentialPermissionsGranted(): " +
 							"locationPermission? %s, " +
@@ -87,7 +88,7 @@ class ConditionManager29 extends AbstractConditionManager {
 			return false;
 		}
 		// ensure location is enabled (if needed on this device)
-		if (!isLocationEnabled()) {
+		if (!isLocationEnabledForWiFi(ctx)) {
 			showLocationDialog(ctx, false);
 			return false;
 		}
@@ -98,8 +99,8 @@ class ConditionManager29 extends AbstractConditionManager {
 			int res = SDK_INT >= 31 ?
 					R.string.permission_hotspot_location_denied_precise_body :
 					R.string.permission_hotspot_location_denied_body;
-			showDenialDialog(ctx, R.string.permission_location_title, res,
-					getGoToSettingsListener(ctx),
+			PermissionUtils.showDenialDialog(ctx,
+					R.string.permission_location_title, res,
 					() -> permissionUpdateCallback.accept(false));
 			return false;
 		}
@@ -149,12 +150,4 @@ class ConditionManager29 extends AbstractConditionManager {
 		wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
 	}
 
-	private boolean isLocationEnabled() {
-		if (SDK_INT >= 31) {
-			LocationManager lm = ctx.getSystemService(LocationManager.class);
-			return lm.isLocationEnabled();
-		}
-		return true;
-	}
-
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/CameraPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/CameraPermissionManager.java
index 34e8191852949581b93f677a0bc8d4a0feb99826..331833c5c265f9229e7dabfcc0eaddb9f250b22a 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/CameraPermissionManager.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/CameraPermissionManager.java
@@ -14,8 +14,8 @@ import static android.Manifest.permission.CAMERA;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
 import static androidx.core.content.ContextCompat.checkSelfPermission;
-import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
-import static org.briarproject.briar.android.util.UiUtils.showRationale;
+import static org.briarproject.briar.android.util.PermissionUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showRationale;
 
 class CameraPermissionManager {
 
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/TransportsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/TransportsActivity.java
index 283b9f0eba2d09998931314295bd706ca4143532..65ebccb0755e5499da498bcd09c726e7059781f6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/TransportsActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/TransportsActivity.java
@@ -53,13 +53,12 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING
 import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
 import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
 import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
-import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
-import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.areBluetoothPermissionsGranted;
+import static org.briarproject.briar.android.util.PermissionUtils.requestBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showRationale;
+import static org.briarproject.briar.android.util.PermissionUtils.wasGrantedBluetoothPermissions;
 import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
-import static org.briarproject.briar.android.util.UiUtils.showRationale;
-import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
 
 @MethodsNotNullByDefault
 @ParametersNotNullByDefault
@@ -220,8 +219,7 @@ public class TransportsActivity extends BriarActivity {
 	}
 
 	private void onClicked(TransportId transportId, boolean enable) {
-		if (enable && SDK_INT >= 31 &&
-				(!hasBtConnectPermission(this) || !hasBtScanPermission(this))) {
+		if (enable && SDK_INT >= 31 && !areBluetoothPermissionsGranted(this)) {
 			if (shouldShowRequestPermissionRationale(BLUETOOTH_CONNECT)) {
 				showRationale(this, R.string.permission_bluetooth_title,
 						R.string.permission_bluetooth_body,
@@ -354,9 +352,10 @@ public class TransportsActivity extends BriarActivity {
 
 	@RequiresApi(31)
 	private void handleBtPermissionResult(Map<String, Boolean> grantedMap) {
-		if (wasGrantedBluetoothPermissions(grantedMap)) {
+		if (wasGrantedBluetoothPermissions(this, grantedMap)) {
 			viewModel.enableTransport(BluetoothConstants.ID, true);
 		} else {
+			// update adapter to reflect the "off" toggle state after denying
 			transportsAdapter.notifyDataSetChanged();
 			showDenialDialog(this,
 					R.string.permission_bluetooth_title,
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java
index 08322c510421abc86913d89da1100be146e2451f..89b12804549df2c53c0da5ae2e83d1223b723428 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java
@@ -61,10 +61,10 @@ import static java.util.Objects.requireNonNull;
 import static java.util.TimeZone.getTimeZone;
 import static org.briarproject.bramble.util.AndroidUtils.getBluetoothAddressAndMethod;
 import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
 import static org.briarproject.bramble.util.PrivacyUtils.scrubInetAddress;
 import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
 import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
+import static org.briarproject.briar.android.util.PermissionUtils.areBluetoothPermissionsGranted;
 
 @Immutable
 @NotNullByDefault
@@ -289,7 +289,8 @@ class BriarReportCollector {
 
 			// Is Bluetooth connectable?
 			@SuppressLint("MissingPermission")
-			int scanMode = hasBtScanPermission(ctx) ? bt.getScanMode() : -1;
+			int scanMode = areBluetoothPermissionsGranted(ctx) ?
+					bt.getScanMode() : -1;
 			boolean btConnectable = scanMode == SCAN_MODE_CONNECTABLE ||
 					scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
 			connectivityInfo.add("BluetoothConnectable", btConnectable);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java
index 465de4441a36eff76d40f76d52351c1fbc7138ea..3f528d96b04c68c7198c30c37789ff78ee72be90 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java
@@ -26,14 +26,13 @@ import androidx.preference.SwitchPreferenceCompat;
 
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.os.Build.VERSION.SDK_INT;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
-import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
 import static org.briarproject.briar.android.AppModule.getAndroidComponent;
 import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist;
-import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
-import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
-import static org.briarproject.briar.android.util.UiUtils.showRationale;
-import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.areBluetoothPermissionsGranted;
+import static org.briarproject.briar.android.util.PermissionUtils.requestBluetoothPermissions;
+import static org.briarproject.briar.android.util.PermissionUtils.showDenialDialog;
+import static org.briarproject.briar.android.util.PermissionUtils.showRationale;
+import static org.briarproject.briar.android.util.PermissionUtils.wasGrantedBluetoothPermissions;
 
 @MethodsNotNullByDefault
 @ParametersNotNullByDefault
@@ -91,7 +90,7 @@ public class ConnectionsFragment extends PreferenceFragmentCompat {
 		if (SDK_INT >= 31) {
 			enableBluetooth.setOnPreferenceChangeListener((p, value) -> {
 				FragmentActivity ctx = requireActivity();
-				if (hasBtConnectPermission(ctx) && hasBtScanPermission(ctx)) {
+				if (areBluetoothPermissionsGranted(ctx)) {
 					return true;
 				} else if (shouldShowRequestPermissionRationale(
 						BLUETOOTH_CONNECT)) {
@@ -160,7 +159,7 @@ public class ConnectionsFragment extends PreferenceFragmentCompat {
 
 	@RequiresApi(31)
 	private void handleBtPermissionResult(Map<String, Boolean> grantedMap) {
-		if (wasGrantedBluetoothPermissions(grantedMap)) {
+		if (wasGrantedBluetoothPermissions(requireActivity(), grantedMap)) {
 			enableBluetooth.setChecked(true);
 		} else {
 			showDenialDialog(requireActivity(),
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdad47615c00e2dbd83e8895c06618add6fcd367
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java
@@ -0,0 +1,176 @@
+package org.briarproject.briar.android.util;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.briarproject.briar.R;
+import org.briarproject.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.nullsafety.ParametersNotNullByDefault;
+
+import java.util.Map;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+
+import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.content.Intent.CATEGORY_DEFAULT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
+import static android.widget.Toast.LENGTH_LONG;
+import static androidx.core.content.ContextCompat.checkSelfPermission;
+import static java.lang.Boolean.TRUE;
+import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class PermissionUtils {
+
+	public static boolean gotPermission(Context ctx,
+			@Nullable Map<String, Boolean> grantedMap, String permission) {
+		if (grantedMap == null || !grantedMap.containsKey(permission)) {
+			return isPermissionGranted(ctx, permission);
+		} else {
+			return TRUE.equals(grantedMap.get(permission));
+		}
+	}
+
+	public static boolean isPermissionGranted(Context ctx, String permission) {
+		return checkSelfPermission(ctx, permission) ==
+				PERMISSION_GRANTED;
+	}
+
+	public static boolean areBluetoothPermissionsGranted(Context ctx) {
+		if (SDK_INT < 31) return true;
+		return isPermissionGranted(ctx, BLUETOOTH_ADVERTISE) &&
+				isPermissionGranted(ctx, BLUETOOTH_CONNECT) &&
+				isPermissionGranted(ctx, BLUETOOTH_SCAN);
+	}
+
+	@RequiresApi(31)
+	public static boolean wasGrantedBluetoothPermissions(Context ctx,
+			@Nullable Map<String, Boolean> grantedMap) {
+		return grantedMap != null &&
+				gotPermission(ctx, grantedMap, BLUETOOTH_ADVERTISE) &&
+				gotPermission(ctx, grantedMap, BLUETOOTH_CONNECT) &&
+				gotPermission(ctx, grantedMap, BLUETOOTH_SCAN);
+	}
+
+	public static DialogInterface.OnClickListener getGoToSettingsListener(
+			Context context) {
+		return (dialog, which) -> {
+			Intent i = new Intent();
+			i.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
+			i.addCategory(CATEGORY_DEFAULT);
+			i.setData(Uri.parse("package:" + APPLICATION_ID));
+			i.addFlags(FLAG_ACTIVITY_NEW_TASK);
+			context.startActivity(i);
+		};
+	}
+
+	/**
+	 * @return true if location is enabled for Bluetooth purposes,
+	 * or it isn't required due to this being a device with SDK < 28 or >= 31.
+	 */
+	public static boolean isLocationEnabledForBt(Context ctx) {
+		if (SDK_INT >= 28 && SDK_INT < 31) {
+			LocationManager lm = ctx.getSystemService(LocationManager.class);
+			return lm.isLocationEnabled();
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * @return true if location is enabled for Wi-Fi hotspot purposes,
+	 * or it isn't required due to this being a device with SDK < 31.
+	 */
+	public static boolean isLocationEnabledForWiFi(Context ctx) {
+		if (SDK_INT >= 31) {
+			LocationManager lm = ctx.getSystemService(LocationManager.class);
+			return lm.isLocationEnabled();
+		}
+		return true;
+	}
+
+	public static void showLocationDialog(Context ctx) {
+		showLocationDialog(ctx, true);
+	}
+
+	public static void showLocationDialog(Context ctx, boolean forBluetooth) {
+		AlertDialog.Builder builder =
+				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
+		builder.setTitle(R.string.permission_location_setting_title);
+		if (forBluetooth) {
+			builder.setMessage(R.string.permission_location_setting_body);
+		} else {
+			builder.setMessage(
+					R.string.permission_location_setting_hotspot_body);
+		}
+		builder.setNegativeButton(R.string.cancel, null);
+		builder.setPositiveButton(R.string.permission_location_setting_button,
+				(dialog, which) -> {
+					Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
+					try {
+						ctx.startActivity(i);
+					} catch (ActivityNotFoundException e) {
+						Toast.makeText(ctx, R.string.error_start_activity,
+								LENGTH_LONG).show();
+					}
+				});
+		builder.show();
+	}
+
+	/**
+	 * This closes the given activity when dialog gets cancelled.
+	 */
+	public static void showDenialDialog(FragmentActivity ctx,
+			@StringRes int title, @StringRes int body) {
+		showDenialDialog(ctx, title, body, ctx::supportFinishAfterTransition);
+	}
+
+	public static void showDenialDialog(FragmentActivity ctx,
+			@StringRes int title, @StringRes int body, Runnable onDenied) {
+		AlertDialog.Builder builder =
+				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
+		builder.setTitle(title);
+		builder.setMessage(body);
+		builder.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx));
+		builder.setNegativeButton(R.string.cancel, (dialog, which) ->
+				onDenied.run());
+		builder.show();
+	}
+
+	public static void showRationale(FragmentActivity ctx, @StringRes int title,
+			@StringRes int body, @Nullable Runnable onOk) {
+		AlertDialog.Builder builder =
+				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
+		builder.setTitle(title);
+		builder.setMessage(body);
+		builder.setNeutralButton(R.string.continue_button, (dialog, which) -> {
+			if (onOk != null) onOk.run();
+			dialog.dismiss();
+		});
+		builder.show();
+	}
+
+	@RequiresApi(31)
+	public static void requestBluetoothPermissions(
+			ActivityResultLauncher<String[]> launcher) {
+		String[] perms = new String[] {BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT,
+				BLUETOOTH_SCAN};
+		launcher.launch(perms);
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
index d3bcaa11fccb2c14c7ede7b37a2e9a48aa01c33b..2a133b6478016ceb16cf454e118ba015beb4b7dd 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
@@ -6,12 +6,9 @@ import android.app.ActivityManager.MemoryInfo;
 import android.app.KeyguardManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
-import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.location.LocationManager;
-import android.net.Uri;
 import android.os.Debug;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -44,7 +41,6 @@ import org.briarproject.nullsafety.MethodsNotNullByDefault;
 import org.briarproject.nullsafety.ParametersNotNullByDefault;
 
 import java.util.Locale;
-import java.util.Map;
 import java.util.logging.Logger;
 
 import androidx.activity.result.ActivityResultLauncher;
@@ -55,7 +51,6 @@ import androidx.annotation.ColorRes;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.annotation.StringRes;
 import androidx.annotation.UiThread;
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.content.ContextCompat;
@@ -71,15 +66,10 @@ import androidx.lifecycle.LiveData;
 import androidx.lifecycle.Observer;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
 
-import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
-import static android.Manifest.permission.BLUETOOTH_CONNECT;
-import static android.Manifest.permission.BLUETOOTH_SCAN;
 import static android.content.Context.KEYGUARD_SERVICE;
-import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Build.MANUFACTURER;
 import static android.os.Build.VERSION.SDK_INT;
-import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.FORMAT_ABBREV_ALL;
 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
@@ -109,13 +99,11 @@ import static androidx.core.content.ContextCompat.getColor;
 import static androidx.core.content.ContextCompat.getSystemService;
 import static androidx.core.graphics.drawable.DrawableCompat.setTint;
 import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
-import static java.lang.Boolean.TRUE;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.logging.Level.WARNING;
 import static java.util.logging.Logger.getLogger;
 import static org.briarproject.bramble.util.LogUtils.logException;
-import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
 import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
 import static org.briarproject.briar.android.TestingConstants.IS_OLD_ANDROID;
 import static org.briarproject.briar.android.TestingConstants.OLD_ANDROID_WARN_DATE;
@@ -330,17 +318,6 @@ public class UiUtils {
 		textView.setMovementMethod(new LinkMovementMethod());
 	}
 
-	public static OnClickListener getGoToSettingsListener(Context context) {
-		return (dialog, which) -> {
-			Intent i = new Intent();
-			i.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
-			i.addCategory(CATEGORY_DEFAULT);
-			i.setData(Uri.parse("package:" + APPLICATION_ID));
-			i.addFlags(FLAG_ACTIVITY_NEW_TASK);
-			context.startActivity(i);
-		};
-	}
-
 	public static void showOnboardingDialog(Context ctx, String text) {
 		new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
 				.setMessage(text)
@@ -349,19 +326,6 @@ public class UiUtils {
 				.show();
 	}
 
-	/**
-	 * @return true if location is enabled,
-	 * or it isn't required due to this being a device with SDK < 28 or >= 31.
-	 */
-	public static boolean isLocationEnabled(Context ctx) {
-		if (SDK_INT >= 28 && SDK_INT < 31) {
-			LocationManager lm = ctx.getSystemService(LocationManager.class);
-			return lm.isLocationEnabled();
-		} else {
-			return true;
-		}
-	}
-
 	public static boolean isSamsung7() {
 		return (SDK_INT == 24 || SDK_INT == 25) &&
 				MANUFACTURER.equalsIgnoreCase("Samsung");
@@ -528,34 +492,6 @@ public class UiUtils {
 		return isoCode;
 	}
 
-	public static void showLocationDialog(Context ctx) {
-		showLocationDialog(ctx, true);
-	}
-
-	public static void showLocationDialog(Context ctx, boolean forBluetooth) {
-		AlertDialog.Builder builder =
-				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
-		builder.setTitle(R.string.permission_location_setting_title);
-		if (forBluetooth) {
-			builder.setMessage(R.string.permission_location_setting_body);
-		} else {
-			builder.setMessage(
-					R.string.permission_location_setting_hotspot_body);
-		}
-		builder.setNegativeButton(R.string.cancel, null);
-		builder.setPositiveButton(R.string.permission_location_setting_button,
-				(dialog, which) -> {
-					Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
-					try {
-						ctx.startActivity(i);
-					} catch (ActivityNotFoundException e) {
-						Toast.makeText(ctx, R.string.error_start_activity,
-								LENGTH_LONG).show();
-					}
-				});
-		builder.show();
-	}
-
 	public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
 		Drawable icon =
 				VectorDrawableCompat.create(ctx.getResources(), resId, null);
@@ -595,29 +531,6 @@ public class UiUtils {
 				SOFT_INPUT_STATE_HIDDEN);
 	}
 
-	public static void showDenialDialog(FragmentActivity ctx,
-			@StringRes int title, @StringRes int body) {
-		AlertDialog.Builder builder =
-				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
-		builder.setTitle(title);
-		builder.setMessage(body);
-		builder.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx));
-		builder.setNegativeButton(R.string.cancel,
-				(dialog, which) -> ctx.supportFinishAfterTransition());
-		builder.show();
-	}
-
-	public static void showRationale(FragmentActivity ctx, @StringRes int title,
-			@StringRes int body, Runnable requestPermissions) {
-		AlertDialog.Builder builder =
-				new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
-		builder.setTitle(title);
-		builder.setMessage(body);
-		builder.setNeutralButton(R.string.continue_button,
-				(dialog, which) -> requestPermissions.run());
-		builder.show();
-	}
-
 	public static void launchActivityToOpenFile(Context ctx,
 			@Nullable ActivityResultLauncher<String[]> docLauncher,
 			ActivityResultLauncher<String> contentLauncher,
@@ -640,20 +553,4 @@ public class UiUtils {
 		Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG).show();
 	}
 
-	@RequiresApi(31)
-	public static void requestBluetoothPermissions(
-			ActivityResultLauncher<String[]> launcher) {
-		String[] perms = new String[] {BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT,
-				BLUETOOTH_SCAN};
-		launcher.launch(perms);
-	}
-
-	@RequiresApi(31)
-	public static boolean wasGrantedBluetoothPermissions(
-			@Nullable Map<String, Boolean> grantedMap) {
-		return grantedMap != null &&
-				TRUE.equals(grantedMap.get(BLUETOOTH_ADVERTISE)) &&
-				TRUE.equals(grantedMap.get(BLUETOOTH_CONNECT)) &&
-				TRUE.equals(grantedMap.get(BLUETOOTH_SCAN));
-	}
 }
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index f01f4257b93f3d662c8e2510eec1e979f8563cd6..a5125ce0c63baa6d50b12dc648dc7f2a98bf1e6f 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -353,6 +353,7 @@
 	<string name="connect_via_bluetooth_intro">In case Bluetooth connections do not work automatically, you can use this screen to connect manually.\n\nYour contact needs to be nearby for this to work.\n\nYou and your contact should both press \"Start\" at the same time.</string>
 	<string name="connect_via_bluetooth_already_discovering">Already trying to connect via Bluetooth. Please try again shortly.</string>
 	<string name="connect_via_bluetooth_no_location_permission">Cannot continue without location permission</string>
+	<string name="connect_via_bluetooth_no_bluetooth_permission">Cannot continue without nearby devices permission</string>
 	<string name="connect_via_bluetooth_start">Connecting via Bluetooth…</string>
 	<string name="connect_via_bluetooth_success">Successfully connected via Bluetooth</string>
 	<string name="connect_via_bluetooth_error">Could not connect via Bluetooth.</string>