Merge pull request #515 from andrekir/targetsdk31

update targetSdkVersion to 31
master
Andre K 2022-11-04 18:47:54 -03:00 zatwierdzone przez GitHub
commit f56f5a1a5f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 99 dodań i 146 usunięć

Wyświetl plik

@ -42,7 +42,7 @@ android {
defaultConfig {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 30
targetSdkVersion 31
versionCode 30000 // format is Mmmss (where M is 1+the numeric major number
versionName "2.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

Wyświetl plik

@ -11,23 +11,21 @@
android:name="android.hardware.location.gps"
android:required="false" />
<!-- TODO - wait for targetSdkVersion 31
&lt;!&ndash; Request legacy Bluetooth permissions on older devices &ndash;&gt;
<!-- 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" />
&lt;!&ndash; API 31+ Bluetooth permissions &ndash;&gt;
<!-- API 31+ Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<!-- 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" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- This permission is required for analytics - and soon the MQTT gateway -->

Wyświetl plik

@ -8,7 +8,10 @@ import android.content.pm.PackageManager
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.net.Uri
import android.os.*
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.RemoteException
import android.text.method.LinkMovementMethod
import android.view.Menu
import android.view.MenuItem
@ -19,20 +22,14 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.geeksville.mesh.android.BindFailedException
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.ServiceClient
import com.geeksville.mesh.android.*
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.android.getMissingPermissions
import com.geeksville.mesh.android.isGooglePlayAvailable
import com.geeksville.mesh.databinding.ActivityMainBinding
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.BluetoothViewModel
@ -130,8 +127,9 @@ class MainActivity : BaseActivity(), Logging {
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (!permissions.entries.all { it.value }) {
errormsg("User denied permissions")
showSnackbar(getString(R.string.permission_missing_31))
showSnackbar(permissionMissing)
}
requestedEnable = false
bluetoothViewModel.permissionsUpdated()
}
@ -181,71 +179,6 @@ class MainActivity : BaseActivity(), Logging {
}
}
/** Get the minimum permissions our app needs to run correctly
*/
private fun getMinimumPermissions(): Array<String> {
val perms = mutableListOf<String>()
// We only need this for logging to capture files for the simulator - turn off for production
// perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
/* TODO - wait for targetSdkVersion 31
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
perms.add(Manifest.permission.BLUETOOTH_SCAN)
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
}
*/
return getMissingPermissions(perms)
}
/** Possibly prompt user to grant permissions
* @param shouldShowDialog usually true, but in cases where we've already shown a dialog elsewhere we skip it.
*
* @return true if we already have the needed permissions
*/
private fun requestPermission(
missingPerms: Array<String> = getMinimumPermissions(),
shouldShowDialog: Boolean = true
): Boolean =
if (missingPerms.isNotEmpty()) {
val shouldShow = missingPerms.filter {
ActivityCompat.shouldShowRequestPermissionRationale(this, it)
}
fun doRequest() {
info("requesting permissions")
// Ask for all the missing perms
requestPermissionsLauncher.launch(missingPerms)
}
if (shouldShow.isNotEmpty() && shouldShowDialog) {
// DID_REQUEST_PERM is an
// app-defined int constant. The callback method gets the
// result of the request.
warn("Permissions $shouldShow missing, we should show dialog")
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.required_permissions))
.setMessage(getString(R.string.permission_missing_31))
.setNeutralButton(R.string.cancel) { _, _ ->
warn("User bailed due to permissions")
}
.setPositiveButton(R.string.accept) { _, _ ->
doRequest()
}
.show()
} else {
info("Permissions $missingPerms missing, no need to show dialog, just asking OS")
doRequest()
}
false
} else {
// Permission has already been granted
debug("We have our required permissions")
true
}
/// Ask user to rate in play store
private fun askToRate() {
exceptionReporter { // we don't want to crash our app because of bugs in this optional feature
@ -296,9 +229,6 @@ class MainActivity : BaseActivity(), Logging {
handleIntent(intent)
if (isGooglePlayAvailable(this)) askToRate()
// if (!isInTestLab) - very important - even in test lab we must request permissions because we need location perms for some of our tests to pass
requestPermission()
}
private fun initToolbar() {
@ -728,11 +658,22 @@ class MainActivity : BaseActivity(), Logging {
super.onStart()
bluetoothViewModel.enabled.observe(this) { enabled ->
if (!enabled && !requestedEnable) {
if (!isInTestLab && scanModel.selectedBluetooth) {
requestedEnable = true
if (!enabled && !requestedEnable && scanModel.selectedBluetooth) {
requestedEnable = true
if (hasBluetoothPermission()) {
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()
}
}
}

Wyświetl plik

@ -8,14 +8,15 @@ import android.companion.CompanionDeviceManager
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.usb.UsbManager
import android.os.Build
import androidx.core.content.ContextCompat
import com.geeksville.mesh.MainActivity
import com.geeksville.mesh.R
/**
* @return null on platforms without a BlueTooth driver (i.e. the emulator)
*/
val Context.bluetoothManager: BluetoothManager? get() = getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager?
val Context.bluetoothManager: BluetoothManager?
get() = getSystemService(Context.BLUETOOTH_SERVICE).takeIf { hasBluetoothPermission() } as? BluetoothManager?
val Context.deviceManager: CompanionDeviceManager?
get() {
@ -36,7 +37,7 @@ val Context.locationManager: LocationManager get() = requireNotNull(getSystemSer
* @return true if CompanionDeviceManager API is present
*/
fun Context.hasCompanionDeviceApi(): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O)
packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
else false
@ -51,6 +52,16 @@ fun Context.hasGps(): Boolean = locationManager.allProviders.contains(LocationMa
fun Context.gpsDisabled(): Boolean =
if (hasGps()) !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) else false
/**
* 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) {
getString(R.string.permission_missing)
} else {
getString(R.string.permission_missing_31)
}
/**
* return a list of the permissions we don't have
*/
@ -67,12 +78,12 @@ fun Context.getMissingPermissions(perms: List<String>): Array<String> = perms.fi
fun Context.getBluetoothPermissions(): Array<String> {
val perms = mutableListOf<String>()
/* TODO - wait for targetSdkVersion 31
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
perms.add(Manifest.permission.BLUETOOTH_SCAN)
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
} else if (!hasCompanionDeviceApi()) {
perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
*/
return getMissingPermissions(perms)
}
@ -109,7 +120,7 @@ fun Context.hasLocationPermission() = getLocationPermissions().isEmpty()
fun Context.getBackgroundPermissions(): Array<String> {
val perms = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
if (Build.VERSION.SDK_INT >= 29) // only added later
if (android.os.Build.VERSION.SDK_INT >= 29) // only added later
perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
return getMissingPermissions(perms)

Wyświetl plik

@ -128,10 +128,9 @@ class BTScanModel @Inject constructor(
debug("BTScanModel cleared")
}
private val bluetoothAdapter = context.bluetoothManager?.adapter
private val deviceManager get() = context.deviceManager
val hasCompanionDeviceApi get() = context.hasCompanionDeviceApi()
private val hasBluetoothPermission get() = application.hasBluetoothPermission()
val hasCompanionDeviceApi get() = application.hasCompanionDeviceApi()
val hasBluetoothPermission get() = application.hasBluetoothPermission()
private val usbManager get() = context.usbManager
var selectedAddress: String? = null
@ -219,8 +218,8 @@ class BTScanModel @Inject constructor(
fun setupScan(): Boolean {
selectedAddress = radioInterfaceService.getDeviceAddress()
return if (bluetoothAdapter == null || MockInterface.addressValid(context, usbRepository, "")) {
warn("No bluetooth adapter. Running under emulation?")
return if (MockInterface.addressValid(context, usbRepository, "")) {
warn("Running under emulator/test lab")
val testnodes = listOf(
DeviceListEntry("Included simulator", "m", true),
@ -273,14 +272,16 @@ class BTScanModel @Inject constructor(
private var networkDiscovery: Job? = null
fun startScan() {
_spinner.value = true
// Start Network Service Discovery (find TCP devices)
networkDiscovery = nsdRepository.networkDiscoveryFlow()
.onEach { addDevice(TCPDeviceListEntry(it)) }
.launchIn(CoroutineScope(Dispatchers.Main))
if (hasCompanionDeviceApi) {
startCompanionScan()
} else startClassicScan()
if (hasBluetoothPermission) {
if (hasCompanionDeviceApi) startCompanionScan() else startClassicScan()
}
}
@SuppressLint("MissingPermission")
@ -290,7 +291,6 @@ class BTScanModel @Inject constructor(
if (bluetoothLeScanner != null) { // could be null if bluetooth is disabled
debug("starting classic scan")
_spinner.value = true
// filter and only accept devices that have our service
val filter =
@ -373,7 +373,6 @@ class BTScanModel @Inject constructor(
@SuppressLint("NewApi")
private fun startCompanionScan() {
debug("starting companion scan")
_spinner.value = true
deviceManager?.associate(
associationRequest(),
@SuppressLint("NewApi")
@ -479,7 +478,11 @@ class BTScanModel @Inject constructor(
}
val permissionIntent =
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(activity, 0, Intent(ACTION_USB_PERMISSION), 0)
} else {
PendingIntent.getBroadcast(activity, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE)
}
val filter = IntentFilter(ACTION_USB_PERMISSION)
activity.registerReceiver(usbReceiver, filter)
usbManager.requestPermission(it.usb.device, permissionIntent)

Wyświetl plik

@ -56,7 +56,9 @@ class BluetoothRepository @Inject constructor(
}
fun getRemoteDevice(address: String): BluetoothDevice? {
return bluetoothAdapterLazy.get()?.takeIf { isValid(address) }?.getRemoteDevice(address)
return bluetoothAdapterLazy.get()
?.takeIf { application.hasBluetoothPermission() && isValid(address) }
?.getRemoteDevice(address)
}
fun getBluetoothLeScanner(): BluetoothLeScanner? {

Wyświetl plik

@ -1,12 +1,10 @@
package com.geeksville.mesh.repository.radio
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.content.Context
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.bluetoothManager
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.mesh.service.*
@ -110,22 +108,15 @@ class BluetoothInterface(
val BTM_FROMNUM_CHARACTER: UUID =
UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
/// Get our bluetooth adapter (should always succeed except on emulator
private fun getBluetoothAdapter(context: Context): BluetoothAdapter? {
val bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
return bluetoothManager.adapter
}
/** Return true if this address is still acceptable. For BLE that means, still bonded */
@SuppressLint("NewApi", "MissingPermission")
override fun addressValid(
context: Context,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): Boolean {
val allPaired = getBluetoothAdapter(context)?.bondedDevices.orEmpty()
.map { it.address }.toSet()
/// Get our bluetooth adapter (should always succeed except on emulator
val allPaired = context.bluetoothManager?.adapter?.bondedDevices.orEmpty()
.map { it.address }.toSet()
return if (!allPaired.contains(rest)) {
warn("Ignoring stale bond to ${rest.anonymize}")
false
@ -170,7 +161,7 @@ class BluetoothInterface(
init {
// Note: this call does no comms, it just creates the device object (even if the
// device is off/not connected)
val device = getBluetoothAdapter(context)?.getRemoteDevice(address)
val device = context.bluetoothManager?.adapter?.getRemoteDevice(address)
if (device != null) {
info("Creating radio interface service. device=${address.anonymize}")

Wyświetl plik

@ -106,7 +106,11 @@ class MeshServiceNotifications(
)
private val openAppIntent: PendingIntent by lazy {
PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0)
} else {
PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE)
}
}
/**

Wyświetl plik

@ -261,7 +261,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.entries.all { it.value }) {
binding.provideLocationCheckbox.isChecked = true
} else debug("User denied background permission")
} else {
debug("User denied background permission")
showSnackbar(getString(R.string.why_background_required))
}
}
val requestLocationAndBackgroundLauncher =
@ -271,7 +274,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (myActivity.hasBackgroundPermission()) {
binding.provideLocationCheckbox.isChecked = true
} else requestBackgroundAndCheckLauncher.launch(myActivity.getBackgroundPermissions())
} else debug("User denied location permission")
} else {
debug("User denied location permission")
showSnackbar(getString(R.string.why_background_required))
}
}
// init our region spinner
@ -506,37 +512,34 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
val requestPermissionAndScanLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.entries.all { it.value }) {
checkLocationEnabled()
checkBTEnabled()
if (!scanModel.hasCompanionDeviceApi) checkLocationEnabled()
scanLeDevice()
} else {
errormsg("User denied scan permissions")
showSnackbar(getString(R.string.permission_missing))
showSnackbar(requireContext().permissionMissing)
}
bluetoothViewModel.permissionsUpdated()
}
binding.changeRadioButton.setOnClickListener {
debug("User clicked changeRadioButton")
checkBTEnabled()
if ((scanModel.hasCompanionDeviceApi)) {
scanLeDevice()
} else {
// Location is the only runtime permission for classic bluetooth scan
if (myActivity.hasLocationPermission()) {
checkLocationEnabled()
scanLeDevice()
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.required_permissions))
.setMessage(getString(R.string.permission_missing))
.setNeutralButton(R.string.cancel) { _, _ ->
warn("User bailed due to permissions")
}
.setPositiveButton(R.string.accept) { _, _ ->
info("requesting scan permissions")
requestPermissionAndScanLauncher.launch(myActivity.getLocationPermissions())
}
.show()
}
scanLeDevice()
if (scanModel.hasBluetoothPermission) {
checkBTEnabled()
if (!scanModel.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(myActivity.getBluetoothPermissions())
}
.show()
}
}
}