mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
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>
This commit is contained in:
parent
56760412cb
commit
88f0522927
11 changed files with 25 additions and 122 deletions
|
|
@ -28,17 +28,10 @@
|
|||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Request legacy Bluetooth permissions on older devices -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
<!-- API 31+ Bluetooth permissions -->
|
||||
<!-- API 31+ Bluetooth permissions (Min SDK is 32) -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
|
||||
<!-- API 33+ Notification runtime permissions -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -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}" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
val requiredPermissions = mutableListOf<String>()
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue