From 88f05229279ffc1c3daba12e4cd5af1ee1679af7 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:06:20 -0600 Subject: [PATCH] refactor: Set minSdk to 32 and remove legacy code This commit increases the minimum SDK version to 32 (Android 12L), allowing for the removal of compatibility code for older Android versions. Key changes include: * Updated `MIN_SDK` from 26 to 32. * Removed conditional logic for API levels below 32, particularly for permissions, foreground services, and UI components. * Simplified Bluetooth permission handling to only target modern APIs. * Cleaned up AndroidManifest by removing legacy permission tags. Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- app/src/main/AndroidManifest.xml | 11 +--- .../java/com/geeksville/mesh/MainActivity.kt | 12 ++--- .../geeksville/mesh/service/MeshService.kt | 15 ++---- .../mesh/service/MeshServiceStarter.kt | 11 ++-- .../mesh/ui/connections/ConnectionsScreen.kt | 9 +--- .../ui/connections/components/BLEDevices.kt | 50 ++----------------- config.properties | 2 +- .../meshtastic/core/common/ContextServices.kt | 15 +----- .../data/repository/LocationRepository.kt | 2 +- .../core/model/util/DistanceExtensions.kt | 17 ++----- .../org/meshtastic/core/ui/theme/Theme.kt | 3 +- 11 files changed, 25 insertions(+), 122 deletions(-) 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) }