diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d486b3dd45..788b923cca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,17 +28,10 @@
android:name="android.hardware.location.gps"
android:required="false" />
-
-
-
-
-
+
+ android:usesPermissionFlags="neverForLocation" />
diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
index 914d69c799..a2f184f26e 100644
--- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt
+++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
@@ -23,7 +23,6 @@ import android.content.Intent
import android.graphics.Color
import android.hardware.usb.UsbManager
import android.net.Uri
-import android.os.Build
import android.os.Bundle
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
@@ -67,14 +66,9 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
- enableEdgeToEdge(
- // Disable three-button navbar scrim on pre-Q devices
- navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
- )
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- // Disable three-button navbar scrim
- window.setNavigationBarContrastEnforced(false)
- }
+ enableEdgeToEdge(navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT))
+ // Disable three-button navbar scrim (unconditional on API 32+)
+ window.setNavigationBarContrastEnforced(false)
super.onCreate(savedInstanceState)
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
index f99b052243..1273f4780c 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt
@@ -23,7 +23,6 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
-import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
@@ -477,14 +476,10 @@ class MeshService : Service() {
this,
SERVICE_NOTIFY_ID,
notification,
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (hasLocationPermission()) {
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
- } else {
- ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
- }
+ if (hasLocationPermission()) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
} else {
- 0 // No specific type needed for older Android versions
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
},
)
} catch (ex: Exception) {
@@ -1359,7 +1354,7 @@ class MeshService : Service() {
val failure =
when {
address == null -> "no_active_address"
- myNodeNum == null -> "no_my_node"
+ myNodeInfo == null -> "no_my_node"
else -> null
}
if (failure != null) {
@@ -1368,7 +1363,7 @@ class MeshService : Service() {
}
val safeAddress = address!!
- val myNum = myNodeNum!!
+ val myNum = myNodeNum
val storeForwardConfig = moduleConfig.storeForward
val lastRequest = meshPrefs.getStoreForwardLastRequest(safeAddress)
val (window, max) =
diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt
index ed59ff5b2a..2915e477a1 100644
--- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt
@@ -19,7 +19,6 @@ package com.geeksville.mesh.service
import android.app.ForegroundServiceStartNotAllowedException
import android.content.Context
-import android.os.Build
import co.touchlab.kermit.Logger
import com.geeksville.mesh.BuildConfig
@@ -36,13 +35,9 @@ fun MeshService.Companion.startService(context: Context) {
Logger.i { "Trying to start service debug=${BuildConfig.DEBUG}" }
val intent = createIntent(context)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- try {
- context.startForegroundService(intent)
- } catch (ex: ForegroundServiceStartNotAllowedException) {
- Logger.e { "Unable to start service: ${ex.message}" }
- }
- } else {
+ try {
context.startForegroundService(intent)
+ } catch (ex: ForegroundServiceStartNotAllowedException) {
+ Logger.e { "Unable to start service: ${ex.message}" }
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt
index e639d3f7ae..b633b67282 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt
@@ -18,8 +18,6 @@
package com.geeksville.mesh.ui.connections
import android.net.InetAddresses
-import android.os.Build
-import android.util.Patterns
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -93,12 +91,7 @@ import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.feature.settings.radio.component.PacketResponseStateDialog
import org.meshtastic.proto.ConfigProtos
-fun String?.isIPAddress(): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- @Suppress("DEPRECATION")
- this != null && Patterns.IP_ADDRESS.matcher(this).matches()
-} else {
- InetAddresses.isNumericAddress(this.toString())
-}
+fun String?.isIPAddress(): Boolean = InetAddresses.isNumericAddress(this.toString())
/**
* Composable screen for managing device connections (BLE, TCP, USB). It handles permission requests for location and
diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt
index 6d9adf3a7f..e237895027 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt
@@ -19,7 +19,6 @@ package com.geeksville.mesh.ui.connections.components
import android.Manifest
import android.content.Intent
-import android.os.Build
import android.provider.Settings.ACTION_BLUETOOTH_SETTINGS
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -40,7 +39,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -52,8 +50,6 @@ import com.geeksville.mesh.model.DeviceListEntry
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.rememberMultiplePermissionsState
-import com.google.accompanist.permissions.rememberPermissionState
-import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.strings.Res
@@ -63,11 +59,9 @@ import org.meshtastic.core.strings.bluetooth_paired_devices
import org.meshtastic.core.strings.grant_permissions
import org.meshtastic.core.strings.no_ble_devices
import org.meshtastic.core.strings.open_settings
-import org.meshtastic.core.strings.permission_missing
import org.meshtastic.core.strings.permission_missing_31
import org.meshtastic.core.strings.scan
import org.meshtastic.core.strings.scanning_bluetooth
-import org.meshtastic.core.ui.util.showToast
/**
* Composable that displays a list of Bluetooth Low Energy (BLE) devices and allows scanning. It handles Bluetooth
@@ -91,54 +85,21 @@ fun BLEDevices(
scanModel: BTScanModel,
bluetoothEnabled: Boolean,
) {
- LocalContext.current // Used implicitly by stringResource
val isScanning by scanModel.spinner.collectAsStateWithLifecycle(false)
- // Define permissions needed for Bluetooth scanning based on Android version.
+ // Bluetooth permissions for Android 12+ (Min SDK is 32)
val bluetoothPermissionsList = remember {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- listOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT)
- } else {
- listOf(
- Manifest.permission.BLUETOOTH,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION,
- )
- }
+ listOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT)
}
- val context = LocalContext.current
- val permsMissing =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- stringResource(Res.string.permission_missing_31)
- } else {
- stringResource(Res.string.permission_missing)
- }
- val coroutineScope = rememberCoroutineScope()
-
- val singlePermissionState =
- rememberPermissionState(
- permission = Manifest.permission.ACCESS_BACKGROUND_LOCATION,
- onPermissionResult = { granted ->
- scanModel.refreshPermissions()
- scanModel.startScan()
- },
- )
-
val permissionsState =
rememberMultiplePermissionsState(
permissions = bluetoothPermissionsList,
onPermissionsResult = { permissions ->
val granted = permissions.values.all { it }
- if (permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)) {
- coroutineScope.launch { context.showToast(permsMissing) }
- singlePermissionState.launchPermissionRequest()
- }
if (granted) {
scanModel.refreshPermissions()
scanModel.startScan()
- } else {
- coroutineScope.launch { context.showToast(permsMissing) }
}
},
)
@@ -231,12 +192,7 @@ fun BLEDevices(
} else {
// Show a message and a button to grant permissions if not all granted
EmptyStateContent(
- text =
- if (permissionsState.shouldShowRationale) {
- stringResource(Res.string.permission_missing)
- } else {
- stringResource(Res.string.permission_missing_31)
- },
+ text = stringResource(Res.string.permission_missing_31),
actionButton = {
Button(onClick = { checkPermissionsAndScan(permissionsState, scanModel, bluetoothEnabled) }) {
Text(text = stringResource(Res.string.grant_permissions))
diff --git a/config.properties b/config.properties
index 9f910ff05e..b8a49613ce 100644
--- a/config.properties
+++ b/config.properties
@@ -20,7 +20,7 @@ VERSION_CODE_OFFSET=29314197
# Application and SDK versions
APPLICATION_ID=com.geeksville.mesh
-MIN_SDK=26
+MIN_SDK=32
TARGET_SDK=36
COMPILE_SDK=36
diff --git a/core/common/src/androidMain/kotlin/org/meshtastic/core/common/ContextServices.kt b/core/common/src/androidMain/kotlin/org/meshtastic/core/common/ContextServices.kt
index 51ed91048c..7015c87ed7 100644
--- a/core/common/src/androidMain/kotlin/org/meshtastic/core/common/ContextServices.kt
+++ b/core/common/src/androidMain/kotlin/org/meshtastic/core/common/ContextServices.kt
@@ -21,7 +21,6 @@ import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.LocationManager
-import android.os.Build
import androidx.core.content.ContextCompat
/** Checks if the device has a GPS receiver. */
@@ -44,22 +43,12 @@ fun Context.gpsDisabled(): Boolean {
* Determines the list of Bluetooth permissions that are currently missing. Internal helper for
* [hasBluetoothPermission].
*
- * For Android S (API 31) and above, this includes [Manifest.permission.BLUETOOTH_SCAN] and
- * [Manifest.permission.BLUETOOTH_CONNECT]. For older versions, it includes [Manifest.permission.ACCESS_FINE_LOCATION]
- * as it is required for Bluetooth scanning.
+ * This includes [Manifest.permission.BLUETOOTH_SCAN] and [Manifest.permission.BLUETOOTH_CONNECT].
*
* @return Array of missing Bluetooth permission strings. Empty if all are granted.
*/
private fun Context.getBluetoothPermissions(): Array {
- val requiredPermissions = mutableListOf()
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- requiredPermissions.add(Manifest.permission.BLUETOOTH_SCAN)
- requiredPermissions.add(Manifest.permission.BLUETOOTH_CONNECT)
- } else {
- // ACCESS_FINE_LOCATION is required for Bluetooth scanning on pre-S devices.
- requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
- }
+ val requiredPermissions = listOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT)
return requiredPermissions
.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
.toTypedArray()
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
index 35d087bf33..05e9895285 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
@@ -77,7 +77,7 @@ constructor(
val providerList = buildList {
val providers = allProviders
- if (android.os.Build.VERSION.SDK_INT >= 31 && LocationManager.FUSED_PROVIDER in providers) {
+ if (LocationManager.FUSED_PROVIDER in providers) {
add(LocationManager.FUSED_PROVIDER)
} else {
if (LocationManager.GPS_PROVIDER in providers) add(LocationManager.GPS_PROVIDER)
diff --git a/core/model/src/main/kotlin/org/meshtastic/core/model/util/DistanceExtensions.kt b/core/model/src/main/kotlin/org/meshtastic/core/model/util/DistanceExtensions.kt
index 35fec459fb..aef6fbc948 100644
--- a/core/model/src/main/kotlin/org/meshtastic/core/model/util/DistanceExtensions.kt
+++ b/core/model/src/main/kotlin/org/meshtastic/core/model/util/DistanceExtensions.kt
@@ -33,20 +33,9 @@ enum class DistanceUnit(val symbol: String, val multiplier: Float, val system: I
companion object {
fun getFromLocale(locale: Locale = Locale.getDefault()): DisplayUnits =
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
- when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) {
- LocaleData.MeasurementSystem.SI -> DisplayUnits.METRIC
- else -> DisplayUnits.IMPERIAL
- }
- } else {
- when (locale.country.uppercase(locale)) {
- "US",
- "LR",
- "MM",
- "GB",
- -> DisplayUnits.IMPERIAL
- else -> DisplayUnits.METRIC
- }
+ when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) {
+ LocaleData.MeasurementSystem.SI -> DisplayUnits.METRIC
+ else -> DisplayUnits.IMPERIAL
}
}
}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/theme/Theme.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/theme/Theme.kt
index ec1d09cdb7..25756d6faa 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/theme/Theme.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/theme/Theme.kt
@@ -19,7 +19,6 @@
package org.meshtastic.core.ui.theme
-import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
@@ -283,7 +282,7 @@ fun AppTheme(
) {
val colorScheme =
when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ dynamicColor -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}