kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
chore: update compileSdk and targetSdk to API 33
rodzic
ef11af6e0b
commit
15ed09680f
|
@ -21,6 +21,8 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace 'com.geeksville.mesh'
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
|
@ -29,11 +31,11 @@ android {
|
|||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
compileSdkVersion 33
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
applicationId "com.geeksville.mesh"
|
||||
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
|
||||
targetSdkVersion 31
|
||||
targetSdk 33
|
||||
versionCode 30202 // format is Mmmss (where M is 1+the numeric major number
|
||||
versionName "2.2.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
@ -41,7 +43,7 @@ android {
|
|||
// per https://developer.android.com/studio/write/vector-asset-studio
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
flavorDimensions 'default'
|
||||
flavorDimensions = ['default']
|
||||
productFlavors {
|
||||
fdroid {
|
||||
dimension = 'default'
|
||||
|
@ -98,7 +100,6 @@ android {
|
|||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
namespace 'com.geeksville.mesh'
|
||||
}
|
||||
|
||||
// per protobuf-gradle-plugin docs, this is recommended for android
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<!-- API 33+ Notification runtime permissions -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<!-- Permissions required for providing location (from phone GPS) to mesh -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
@ -57,6 +60,10 @@
|
|||
<!-- zxing library for QR Code scanning using camera -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
|
|
|
@ -117,16 +117,28 @@ class MainActivity : AppCompatActivity(), Logging {
|
|||
private val bluetoothViewModel: BluetoothViewModel by viewModels()
|
||||
private val model: UIViewModel by viewModels()
|
||||
|
||||
private val requestPermissionsLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||
if (!permissions.entries.all { it.value }) {
|
||||
errormsg("User denied permissions")
|
||||
private val bluetoothPermissionsLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
if (result.entries.all { it.value }) {
|
||||
info("Bluetooth permissions granted")
|
||||
} else {
|
||||
warn("Bluetooth permissions denied")
|
||||
showSnackbar(permissionMissing)
|
||||
}
|
||||
requestedEnable = false
|
||||
bluetoothViewModel.permissionsUpdated()
|
||||
}
|
||||
|
||||
private val notificationPermissionsLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
if (result.entries.all { it.value }) {
|
||||
info("Notification permissions granted")
|
||||
} else {
|
||||
warn("Notification permissions denied")
|
||||
showSnackbar(getString(R.string.notification_denied))
|
||||
}
|
||||
}
|
||||
|
||||
data class TabInfo(val text: String, val icon: Int, val content: Fragment)
|
||||
|
||||
private val tabInfos = arrayOf(
|
||||
|
@ -385,6 +397,17 @@ class MainActivity : AppCompatActivity(), Logging {
|
|||
if (model.provideLocation.value == true)
|
||||
service.startProvideLocation()
|
||||
}
|
||||
|
||||
if (!hasNotificationPermission()) {
|
||||
val notificationPermissions = getNotificationPermissions()
|
||||
rationaleDialog(
|
||||
shouldShowRequestPermissionRationale(notificationPermissions),
|
||||
R.string.notification_required,
|
||||
getString(R.string.why_notification_required),
|
||||
) {
|
||||
notificationPermissionsLauncher.launch(notificationPermissions)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For other connection states, just slam them in
|
||||
model.setConnectionState(newConnection)
|
||||
|
@ -640,17 +663,10 @@ class MainActivity : AppCompatActivity(), Logging {
|
|||
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||
bleRequestEnable.launch(enableBtIntent)
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.required_permissions))
|
||||
.setMessage(permissionMissing)
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
warn("User bailed due to permissions")
|
||||
}
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
info("requesting permissions")
|
||||
requestPermissionsLauncher.launch(getBluetoothPermissions())
|
||||
}
|
||||
.show()
|
||||
val bluetoothPermissions = getBluetoothPermissions()
|
||||
rationaleDialog(shouldShowRequestPermissionRationale(bluetoothPermissions)) {
|
||||
bluetoothPermissionsLauncher.launch(bluetoothPermissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,19 @@ package com.geeksville.mesh.android
|
|||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.NotificationManager
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.location.LocationManager
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.location.LocationManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.geeksville.mesh.R
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
/**
|
||||
* @return null on platforms without a BlueTooth driver (i.e. the emulator)
|
||||
|
@ -48,7 +52,7 @@ fun Context.gpsDisabled(): Boolean =
|
|||
if (hasGps()) !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) else false
|
||||
|
||||
/**
|
||||
* return the text string of the permissions missing
|
||||
* @return the text string of the permissions missing
|
||||
*/
|
||||
val Context.permissionMissing: String
|
||||
get() = if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) {
|
||||
|
@ -57,6 +61,55 @@ val Context.permissionMissing: String
|
|||
getString(R.string.permission_missing_31)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any given permissions need to show rationale.
|
||||
*
|
||||
* @return true if should show UI with rationale before requesting a permission.
|
||||
*/
|
||||
fun Activity.shouldShowRequestPermissionRationale(permissions: Array<String>): Boolean {
|
||||
for (permission in permissions) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any given permissions need to show rationale.
|
||||
*
|
||||
* @return true if should show UI with rationale before requesting a permission.
|
||||
*/
|
||||
fun Fragment.shouldShowRequestPermissionRationale(permissions: Array<String>): Boolean {
|
||||
for (permission in permissions) {
|
||||
if (shouldShowRequestPermissionRationale(permission)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles whether a rationale dialog should be shown before performing an action.
|
||||
*/
|
||||
fun Context.rationaleDialog(
|
||||
shouldShowRequestPermissionRationale: Boolean = true,
|
||||
title: Int = R.string.required_permissions,
|
||||
rationale: CharSequence = permissionMissing,
|
||||
invokeFun: () -> Unit,
|
||||
) {
|
||||
if (!shouldShowRequestPermissionRationale) invokeFun()
|
||||
else MaterialAlertDialogBuilder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(rationale)
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
}
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
invokeFun()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* return a list of the permissions we don't have
|
||||
*/
|
||||
|
@ -123,3 +176,17 @@ fun Context.getBackgroundPermissions(): Array<String> {
|
|||
|
||||
/** @return true if the user already has background location permission */
|
||||
fun Context.hasBackgroundPermission() = getBackgroundPermissions().isEmpty()
|
||||
|
||||
/**
|
||||
* Notification permission (or empty if we already have what we need)
|
||||
*/
|
||||
fun Context.getNotificationPermissions(): Array<String> {
|
||||
val perms = mutableListOf<String>()
|
||||
if (android.os.Build.VERSION.SDK_INT >= 33)
|
||||
perms.add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
return getMissingPermissions(perms)
|
||||
}
|
||||
|
||||
/** @return true if the user already has notification permission */
|
||||
fun Context.hasNotificationPermission() = getNotificationPermissions().isEmpty()
|
||||
|
|
|
@ -662,11 +662,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
val requestPermissionAndScanLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||
if (permissions.entries.all { it.value }) {
|
||||
info("Bluetooth permissions granted")
|
||||
checkBTEnabled()
|
||||
if (!hasCompanionDeviceApi) checkLocationEnabled()
|
||||
scanLeDevice()
|
||||
} else {
|
||||
errormsg("User denied scan permissions")
|
||||
warn("Bluetooth permissions denied")
|
||||
model.showSnackbar(requireContext().permissionMissing)
|
||||
}
|
||||
bluetoothViewModel.permissionsUpdated()
|
||||
|
@ -675,21 +676,16 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
binding.changeRadioButton.setOnClickListener {
|
||||
debug("User clicked changeRadioButton")
|
||||
scanLeDevice()
|
||||
if (requireContext().hasBluetoothPermission()) {
|
||||
val bluetoothPermissions = requireContext().getBluetoothPermissions()
|
||||
if (bluetoothPermissions.isEmpty()) {
|
||||
checkBTEnabled()
|
||||
if (!hasCompanionDeviceApi) checkLocationEnabled()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.required_permissions))
|
||||
.setMessage(requireContext().permissionMissing)
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
warn("User bailed due to permissions")
|
||||
}
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
info("requesting scan permissions")
|
||||
requestPermissionAndScanLauncher.launch(requireContext().getBluetoothPermissions())
|
||||
}
|
||||
.show()
|
||||
requireContext().rationaleDialog(
|
||||
shouldShowRequestPermissionRationale(bluetoothPermissions)
|
||||
) {
|
||||
requestPermissionAndScanLauncher.launch(bluetoothPermissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,9 @@
|
|||
<string name="provide_location_to_mesh">Provide phone location to mesh</string>
|
||||
<string name="camera_required">Camera permission</string>
|
||||
<string name="why_camera_required">We must be granted access to the camera to read QR codes. No pictures or videos will be saved.</string>
|
||||
<string name="notification_required">Notification permission</string>
|
||||
<string name="why_notification_required">Meshtastic needs permission for service and message notifications.</string>
|
||||
<string name="notification_denied">Notification permission denied. To turn on notifications, access: Android Settings > Apps > Meshtastic > Notifications.</string>
|
||||
<string name="modem_config_slow_short">Short Range / Slow</string>
|
||||
<string name="modem_config_slow_medium">Medium Range / Slow</string>
|
||||
<plurals name="delete_messages">
|
||||
|
|
|
@ -4,20 +4,27 @@
|
|||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Ensure important default jvmargs aren't overwritten. See https://github.com/gradle/gradle/issues/19750
|
||||
org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
|
||||
# Disable build features that are enabled by default,
|
||||
# https://developer.android.com/build/releases/gradle-plugin#default-changes
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
Ładowanie…
Reference in New Issue