sforkowany z mirror/meshtastic-android
commit
f56f5a1a5f
|
@ -42,7 +42,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.geeksville.mesh"
|
applicationId "com.geeksville.mesh"
|
||||||
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
|
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
|
versionCode 30000 // format is Mmmss (where M is 1+the numeric major number
|
||||||
versionName "2.0.0"
|
versionName "2.0.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
|
@ -11,23 +11,21 @@
|
||||||
android:name="android.hardware.location.gps"
|
android:name="android.hardware.location.gps"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<!-- TODO - wait for targetSdkVersion 31
|
<!-- Request legacy Bluetooth permissions on older devices -->
|
||||||
<!– Request legacy Bluetooth permissions on older devices –>
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
|
|
||||||
<!– API 31+ Bluetooth permissions –>
|
<!-- API 31+ Bluetooth permissions -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
android:usesPermissionFlags="neverForLocation" />
|
android:usesPermissionFlags="neverForLocation"
|
||||||
-->
|
tools:targetApi="s" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
||||||
|
|
||||||
<!-- Permissions required for providing location (from phone GPS) to mesh -->
|
<!-- 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_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|
||||||
<!-- This permission is required for analytics - and soon the MQTT gateway -->
|
<!-- This permission is required for analytics - and soon the MQTT gateway -->
|
||||||
|
|
|
@ -8,7 +8,10 @@ import android.content.pm.PackageManager
|
||||||
import android.hardware.usb.UsbDevice
|
import android.hardware.usb.UsbDevice
|
||||||
import android.hardware.usb.UsbManager
|
import android.hardware.usb.UsbManager
|
||||||
import android.net.Uri
|
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.text.method.LinkMovementMethod
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -19,20 +22,14 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.geeksville.mesh.android.BindFailedException
|
import com.geeksville.mesh.android.*
|
||||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
|
||||||
import com.geeksville.mesh.android.Logging
|
|
||||||
import com.geeksville.mesh.android.ServiceClient
|
|
||||||
import com.geeksville.mesh.concurrent.handledLaunch
|
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.databinding.ActivityMainBinding
|
||||||
import com.geeksville.mesh.model.BTScanModel
|
import com.geeksville.mesh.model.BTScanModel
|
||||||
import com.geeksville.mesh.model.BluetoothViewModel
|
import com.geeksville.mesh.model.BluetoothViewModel
|
||||||
|
@ -130,8 +127,9 @@ class MainActivity : BaseActivity(), Logging {
|
||||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||||
if (!permissions.entries.all { it.value }) {
|
if (!permissions.entries.all { it.value }) {
|
||||||
errormsg("User denied permissions")
|
errormsg("User denied permissions")
|
||||||
showSnackbar(getString(R.string.permission_missing_31))
|
showSnackbar(permissionMissing)
|
||||||
}
|
}
|
||||||
|
requestedEnable = false
|
||||||
bluetoothViewModel.permissionsUpdated()
|
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
|
/// Ask user to rate in play store
|
||||||
private fun askToRate() {
|
private fun askToRate() {
|
||||||
exceptionReporter { // we don't want to crash our app because of bugs in this optional feature
|
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)
|
handleIntent(intent)
|
||||||
|
|
||||||
if (isGooglePlayAvailable(this)) askToRate()
|
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() {
|
private fun initToolbar() {
|
||||||
|
@ -728,11 +658,22 @@ class MainActivity : BaseActivity(), Logging {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
bluetoothViewModel.enabled.observe(this) { enabled ->
|
bluetoothViewModel.enabled.observe(this) { enabled ->
|
||||||
if (!enabled && !requestedEnable) {
|
if (!enabled && !requestedEnable && scanModel.selectedBluetooth) {
|
||||||
if (!isInTestLab && scanModel.selectedBluetooth) {
|
requestedEnable = true
|
||||||
requestedEnable = true
|
if (hasBluetoothPermission()) {
|
||||||
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||||
bleRequestEnable.launch(enableBtIntent)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,15 @@ import android.companion.CompanionDeviceManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.hardware.usb.UsbManager
|
import android.hardware.usb.UsbManager
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.geeksville.mesh.MainActivity
|
import com.geeksville.mesh.MainActivity
|
||||||
|
import com.geeksville.mesh.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return null on platforms without a BlueTooth driver (i.e. the emulator)
|
* @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?
|
val Context.deviceManager: CompanionDeviceManager?
|
||||||
get() {
|
get() {
|
||||||
|
@ -36,7 +37,7 @@ val Context.locationManager: LocationManager get() = requireNotNull(getSystemSer
|
||||||
* @return true if CompanionDeviceManager API is present
|
* @return true if CompanionDeviceManager API is present
|
||||||
*/
|
*/
|
||||||
fun Context.hasCompanionDeviceApi(): Boolean =
|
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)
|
packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
|
||||||
else false
|
else false
|
||||||
|
|
||||||
|
@ -51,6 +52,16 @@ fun Context.hasGps(): Boolean = locationManager.allProviders.contains(LocationMa
|
||||||
fun Context.gpsDisabled(): Boolean =
|
fun Context.gpsDisabled(): Boolean =
|
||||||
if (hasGps()) !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) else false
|
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
|
* 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> {
|
fun Context.getBluetoothPermissions(): Array<String> {
|
||||||
val perms = mutableListOf<String>()
|
val perms = mutableListOf<String>()
|
||||||
|
|
||||||
/* TODO - wait for targetSdkVersion 31
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
perms.add(Manifest.permission.BLUETOOTH_SCAN)
|
perms.add(Manifest.permission.BLUETOOTH_SCAN)
|
||||||
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
|
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||||
|
} else if (!hasCompanionDeviceApi()) {
|
||||||
|
perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
return getMissingPermissions(perms)
|
return getMissingPermissions(perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +120,7 @@ fun Context.hasLocationPermission() = getLocationPermissions().isEmpty()
|
||||||
fun Context.getBackgroundPermissions(): Array<String> {
|
fun Context.getBackgroundPermissions(): Array<String> {
|
||||||
val perms = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
|
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)
|
perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||||
|
|
||||||
return getMissingPermissions(perms)
|
return getMissingPermissions(perms)
|
||||||
|
|
|
@ -128,10 +128,9 @@ class BTScanModel @Inject constructor(
|
||||||
debug("BTScanModel cleared")
|
debug("BTScanModel cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val bluetoothAdapter = context.bluetoothManager?.adapter
|
|
||||||
private val deviceManager get() = context.deviceManager
|
private val deviceManager get() = context.deviceManager
|
||||||
val hasCompanionDeviceApi get() = context.hasCompanionDeviceApi()
|
val hasCompanionDeviceApi get() = application.hasCompanionDeviceApi()
|
||||||
private val hasBluetoothPermission get() = application.hasBluetoothPermission()
|
val hasBluetoothPermission get() = application.hasBluetoothPermission()
|
||||||
private val usbManager get() = context.usbManager
|
private val usbManager get() = context.usbManager
|
||||||
|
|
||||||
var selectedAddress: String? = null
|
var selectedAddress: String? = null
|
||||||
|
@ -219,8 +218,8 @@ class BTScanModel @Inject constructor(
|
||||||
fun setupScan(): Boolean {
|
fun setupScan(): Boolean {
|
||||||
selectedAddress = radioInterfaceService.getDeviceAddress()
|
selectedAddress = radioInterfaceService.getDeviceAddress()
|
||||||
|
|
||||||
return if (bluetoothAdapter == null || MockInterface.addressValid(context, usbRepository, "")) {
|
return if (MockInterface.addressValid(context, usbRepository, "")) {
|
||||||
warn("No bluetooth adapter. Running under emulation?")
|
warn("Running under emulator/test lab")
|
||||||
|
|
||||||
val testnodes = listOf(
|
val testnodes = listOf(
|
||||||
DeviceListEntry("Included simulator", "m", true),
|
DeviceListEntry("Included simulator", "m", true),
|
||||||
|
@ -273,14 +272,16 @@ class BTScanModel @Inject constructor(
|
||||||
|
|
||||||
private var networkDiscovery: Job? = null
|
private var networkDiscovery: Job? = null
|
||||||
fun startScan() {
|
fun startScan() {
|
||||||
|
_spinner.value = true
|
||||||
|
|
||||||
// Start Network Service Discovery (find TCP devices)
|
// Start Network Service Discovery (find TCP devices)
|
||||||
networkDiscovery = nsdRepository.networkDiscoveryFlow()
|
networkDiscovery = nsdRepository.networkDiscoveryFlow()
|
||||||
.onEach { addDevice(TCPDeviceListEntry(it)) }
|
.onEach { addDevice(TCPDeviceListEntry(it)) }
|
||||||
.launchIn(CoroutineScope(Dispatchers.Main))
|
.launchIn(CoroutineScope(Dispatchers.Main))
|
||||||
|
|
||||||
if (hasCompanionDeviceApi) {
|
if (hasBluetoothPermission) {
|
||||||
startCompanionScan()
|
if (hasCompanionDeviceApi) startCompanionScan() else startClassicScan()
|
||||||
} else startClassicScan()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
|
@ -290,7 +291,6 @@ class BTScanModel @Inject constructor(
|
||||||
|
|
||||||
if (bluetoothLeScanner != null) { // could be null if bluetooth is disabled
|
if (bluetoothLeScanner != null) { // could be null if bluetooth is disabled
|
||||||
debug("starting classic scan")
|
debug("starting classic scan")
|
||||||
_spinner.value = true
|
|
||||||
|
|
||||||
// filter and only accept devices that have our service
|
// filter and only accept devices that have our service
|
||||||
val filter =
|
val filter =
|
||||||
|
@ -373,7 +373,6 @@ class BTScanModel @Inject constructor(
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
private fun startCompanionScan() {
|
private fun startCompanionScan() {
|
||||||
debug("starting companion scan")
|
debug("starting companion scan")
|
||||||
_spinner.value = true
|
|
||||||
deviceManager?.associate(
|
deviceManager?.associate(
|
||||||
associationRequest(),
|
associationRequest(),
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
|
@ -479,7 +478,11 @@ class BTScanModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val permissionIntent =
|
val permissionIntent =
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) {
|
||||||
PendingIntent.getBroadcast(activity, 0, Intent(ACTION_USB_PERMISSION), 0)
|
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)
|
val filter = IntentFilter(ACTION_USB_PERMISSION)
|
||||||
activity.registerReceiver(usbReceiver, filter)
|
activity.registerReceiver(usbReceiver, filter)
|
||||||
usbManager.requestPermission(it.usb.device, permissionIntent)
|
usbManager.requestPermission(it.usb.device, permissionIntent)
|
||||||
|
|
|
@ -56,7 +56,9 @@ class BluetoothRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRemoteDevice(address: String): BluetoothDevice? {
|
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? {
|
fun getBluetoothLeScanner(): BluetoothLeScanner? {
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.bluetooth.BluetoothAdapter
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
import android.bluetooth.BluetoothGattService
|
import android.bluetooth.BluetoothGattService
|
||||||
import android.bluetooth.BluetoothManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
|
import com.geeksville.mesh.android.bluetoothManager
|
||||||
import com.geeksville.mesh.concurrent.handledLaunch
|
import com.geeksville.mesh.concurrent.handledLaunch
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||||
import com.geeksville.mesh.service.*
|
import com.geeksville.mesh.service.*
|
||||||
|
@ -110,22 +108,15 @@ class BluetoothInterface(
|
||||||
val BTM_FROMNUM_CHARACTER: UUID =
|
val BTM_FROMNUM_CHARACTER: UUID =
|
||||||
UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
|
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 */
|
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||||
@SuppressLint("NewApi", "MissingPermission")
|
|
||||||
override fun addressValid(
|
override fun addressValid(
|
||||||
context: Context,
|
context: Context,
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||||
rest: String
|
rest: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val allPaired = getBluetoothAdapter(context)?.bondedDevices.orEmpty()
|
/// Get our bluetooth adapter (should always succeed except on emulator
|
||||||
.map { it.address }.toSet()
|
val allPaired = context.bluetoothManager?.adapter?.bondedDevices.orEmpty()
|
||||||
|
.map { it.address }.toSet()
|
||||||
return if (!allPaired.contains(rest)) {
|
return if (!allPaired.contains(rest)) {
|
||||||
warn("Ignoring stale bond to ${rest.anonymize}")
|
warn("Ignoring stale bond to ${rest.anonymize}")
|
||||||
false
|
false
|
||||||
|
@ -170,7 +161,7 @@ class BluetoothInterface(
|
||||||
init {
|
init {
|
||||||
// Note: this call does no comms, it just creates the device object (even if the
|
// Note: this call does no comms, it just creates the device object (even if the
|
||||||
// device is off/not connected)
|
// device is off/not connected)
|
||||||
val device = getBluetoothAdapter(context)?.getRemoteDevice(address)
|
val device = context.bluetoothManager?.adapter?.getRemoteDevice(address)
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
info("Creating radio interface service. device=${address.anonymize}")
|
info("Creating radio interface service. device=${address.anonymize}")
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,11 @@ class MeshServiceNotifications(
|
||||||
)
|
)
|
||||||
|
|
||||||
private val openAppIntent: PendingIntent by lazy {
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -261,7 +261,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||||
if (permissions.entries.all { it.value }) {
|
if (permissions.entries.all { it.value }) {
|
||||||
binding.provideLocationCheckbox.isChecked = true
|
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 =
|
val requestLocationAndBackgroundLauncher =
|
||||||
|
@ -271,7 +274,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
if (myActivity.hasBackgroundPermission()) {
|
if (myActivity.hasBackgroundPermission()) {
|
||||||
binding.provideLocationCheckbox.isChecked = true
|
binding.provideLocationCheckbox.isChecked = true
|
||||||
} else requestBackgroundAndCheckLauncher.launch(myActivity.getBackgroundPermissions())
|
} 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
|
// init our region spinner
|
||||||
|
@ -506,37 +512,34 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
val requestPermissionAndScanLauncher =
|
val requestPermissionAndScanLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||||
if (permissions.entries.all { it.value }) {
|
if (permissions.entries.all { it.value }) {
|
||||||
checkLocationEnabled()
|
checkBTEnabled()
|
||||||
|
if (!scanModel.hasCompanionDeviceApi) checkLocationEnabled()
|
||||||
scanLeDevice()
|
scanLeDevice()
|
||||||
} else {
|
} else {
|
||||||
errormsg("User denied scan permissions")
|
errormsg("User denied scan permissions")
|
||||||
showSnackbar(getString(R.string.permission_missing))
|
showSnackbar(requireContext().permissionMissing)
|
||||||
}
|
}
|
||||||
|
bluetoothViewModel.permissionsUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.changeRadioButton.setOnClickListener {
|
binding.changeRadioButton.setOnClickListener {
|
||||||
debug("User clicked changeRadioButton")
|
debug("User clicked changeRadioButton")
|
||||||
checkBTEnabled()
|
scanLeDevice()
|
||||||
if ((scanModel.hasCompanionDeviceApi)) {
|
if (scanModel.hasBluetoothPermission) {
|
||||||
scanLeDevice()
|
checkBTEnabled()
|
||||||
} else {
|
if (!scanModel.hasCompanionDeviceApi) checkLocationEnabled()
|
||||||
// Location is the only runtime permission for classic bluetooth scan
|
} else {
|
||||||
if (myActivity.hasLocationPermission()) {
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
checkLocationEnabled()
|
.setTitle(getString(R.string.required_permissions))
|
||||||
scanLeDevice()
|
.setMessage(requireContext().permissionMissing)
|
||||||
} else {
|
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
warn("User bailed due to permissions")
|
||||||
.setTitle(getString(R.string.required_permissions))
|
}
|
||||||
.setMessage(getString(R.string.permission_missing))
|
.setPositiveButton(R.string.accept) { _, _ ->
|
||||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
info("requesting scan permissions")
|
||||||
warn("User bailed due to permissions")
|
requestPermissionAndScanLauncher.launch(myActivity.getBluetoothPermissions())
|
||||||
}
|
}
|
||||||
.setPositiveButton(R.string.accept) { _, _ ->
|
.show()
|
||||||
info("requesting scan permissions")
|
|
||||||
requestPermissionAndScanLauncher.launch(myActivity.getLocationPermissions())
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue