combov2: Re-run Bluetooth connection attempts if permissions are missing

Signed-off-by: Carlos Rafael Giani <crg7475@mailbox.org>
This commit is contained in:
Carlos Rafael Giani 2022-12-25 16:13:28 +01:00
parent 6b989ec7aa
commit e60e9a21cc
2 changed files with 107 additions and 36 deletions

View file

@ -25,6 +25,8 @@ import info.nightscout.comboctl.parser.BatteryState
import info.nightscout.comboctl.parser.ReservoirState import info.nightscout.comboctl.parser.ReservoirState
import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.notifications.Notification import info.nightscout.interfaces.notifications.Notification
@ -109,7 +111,9 @@ class ComboV2Plugin @Inject constructor (
private val sp: SP, private val sp: SP,
private val pumpSync: PumpSync, private val pumpSync: PumpSync,
private val dateUtil: DateUtil, private val dateUtil: DateUtil,
private val uiInteraction: UiInteraction private val uiInteraction: UiInteraction,
private val androidPermission: AndroidPermission,
private val config: Config
) : ) :
PumpPluginBase( PumpPluginBase(
PluginDescription() PluginDescription()
@ -286,6 +290,15 @@ class ComboV2Plugin @Inject constructor (
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface") aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
bluetoothInterface = AndroidBluetoothInterface(context) bluetoothInterface = AndroidBluetoothInterface(context)
// Continue initialization in a separate coroutine. This allows us to call
// runWithPermissionCheck(), which will keep trying to run the code block
// until either the necessary Bluetooth permissios are granted, or the
// coroutine is cancelled (see onStop() below).
pumpCoroutineScope.launch {
runWithPermissionCheck(
context, config, aapsLogger, androidPermission,
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
) {
aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface") aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface")
bluetoothInterface!!.setup() bluetoothInterface!!.setup()
@ -310,8 +323,13 @@ class ComboV2Plugin @Inject constructor (
// NOTE: EventInitializationChanged is sent in getPumpStatus() . // NOTE: EventInitializationChanged is sent in getPumpStatus() .
} }
}
}
override fun onStop() { override fun onStop() {
// Cancel any ongoing background coroutines. This includes an ongoing
// unfinished initialization that still waits for the user to grant
// Bluetooth permissions.
pumpCoroutineScope.cancel() pumpCoroutineScope.cancel()
runBlocking { runBlocking {
@ -580,10 +598,15 @@ class ComboV2Plugin @Inject constructor (
var forciblyDisconnectDueToError = false var forciblyDisconnectDueToError = false
try { try {
runWithPermissionCheck(
context, config, aapsLogger, androidPermission,
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
) {
// Set maxNumAttempts to null to turn off the connection attempt limit inside the connect() call. // Set maxNumAttempts to null to turn off the connection attempt limit inside the connect() call.
// The AAPS queue thread will anyway cause the connectionSetupJob to be canceled when its // The AAPS queue thread will anyway cause the connectionSetupJob to be canceled when its
// connection timeout expires, so the Pump class' own connection attempt limiter is redundant. // connection timeout expires, so the Pump class' own connection attempt limiter is redundant.
pump?.connect(maxNumAttempts = null) pump?.connect(maxNumAttempts = null)
}
// No need to set the driver state here, since the pump's stateFlow will announce that. // No need to set the driver state here, since the pump's stateFlow will announce that.
@ -1486,7 +1509,13 @@ class ComboV2Plugin @Inject constructor (
pairingJob = pumpCoroutineScope.async { pairingJob = pumpCoroutineScope.async {
try { try {
val pairingResult = pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed -> // Do the pairing attempt within runWithPermissionCheck()
// since pairing requires Bluetooth permissions.
val pairingResult = runWithPermissionCheck(
context, config, aapsLogger, androidPermission,
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
) {
pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed ->
aapsLogger.info( aapsLogger.info(
LTag.PUMP, LTag.PUMP,
"New pairing PIN request from Combo pump with Bluetooth " + "New pairing PIN request from Combo pump with Bluetooth " +
@ -1495,6 +1524,7 @@ class ComboV2Plugin @Inject constructor (
_previousPairingAttemptFailedFlow.value = previousAttemptFailed _previousPairingAttemptFailedFlow.value = previousAttemptFailed
newPINChannel.receive() newPINChannel.receive()
} ?: throw IllegalStateException("Attempting to access uninitialized pump manager") } ?: throw IllegalStateException("Attempting to access uninitialized pump manager")
}
if (pairingResult !is ComboCtlPumpManager.PairingResult.Success) if (pairingResult !is ComboCtlPumpManager.PairingResult.Success)
return@async return@async

View file

@ -1,8 +1,16 @@
package info.nightscout.pump.combov2 package info.nightscout.pump.combov2
import android.content.Context
import android.os.Build
import info.nightscout.comboctl.android.AndroidBluetoothPermissionException
import info.nightscout.comboctl.main.BasalProfile import info.nightscout.comboctl.main.BasalProfile
import info.nightscout.comboctl.main.NUM_COMBO_BASAL_PROFILE_FACTORS import info.nightscout.comboctl.main.NUM_COMBO_BASAL_PROFILE_FACTORS
import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.profile.Profile as AAPSProfile import info.nightscout.interfaces.profile.Profile as AAPSProfile
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import kotlinx.coroutines.delay
// Utility extension functions for clearer conversion between // Utility extension functions for clearer conversion between
// ComboCtl units and AAPS units. ComboCtl uses integer-encoded // ComboCtl units and AAPS units. ComboCtl uses integer-encoded
@ -23,3 +31,36 @@ fun AAPSProfile.toComboCtlBasalProfile(): BasalProfile {
} }
return BasalProfile(factors) return BasalProfile(factors)
} }
suspend fun <T> runWithPermissionCheck(
context: Context,
config: Config,
aapsLogger: AAPSLogger,
androidPermission: AndroidPermission,
permissionsToCheckFor: Collection<String>,
block: suspend () -> T
): T {
var permissions = permissionsToCheckFor
while (true) {
try {
if (config.PUMPDRIVERS && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val notAllPermissionsGranted = permissions.fold(initial = false) { currentResult, permission ->
return@fold if (androidPermission.permissionNotGranted(context, permission)) {
aapsLogger.debug(LTag.PUMP, "permission $permission was not granted by the user")
true
} else
currentResult
}
if (notAllPermissionsGranted) {
delay(1000) // Wait a little bit before retrying to avoid 100% CPU usage
continue
}
}
return block.invoke()
} catch (e: AndroidBluetoothPermissionException) {
permissions = permissionsToCheckFor union e.missingPermissions
}
}
}