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) }