combov2: Re-run Bluetooth connection attempts if permissions are missing
Signed-off-by: Carlos Rafael Giani <crg7475@mailbox.org>
This commit is contained in:
parent
6b989ec7aa
commit
e60e9a21cc
2 changed files with 107 additions and 36 deletions
|
@ -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,32 +290,46 @@ class ComboV2Plugin @Inject constructor (
|
||||||
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
|
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
|
||||||
bluetoothInterface = AndroidBluetoothInterface(context)
|
bluetoothInterface = AndroidBluetoothInterface(context)
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface")
|
// Continue initialization in a separate coroutine. This allows us to call
|
||||||
bluetoothInterface!!.setup()
|
// 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")
|
||||||
|
bluetoothInterface!!.setup()
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "Setting up pump manager")
|
aapsLogger.debug(LTag.PUMP, "Setting up pump manager")
|
||||||
pumpManager = ComboCtlPumpManager(bluetoothInterface!!, pumpStateStore)
|
pumpManager = ComboCtlPumpManager(bluetoothInterface!!, pumpStateStore)
|
||||||
pumpManager!!.setup {
|
pumpManager!!.setup {
|
||||||
_pairedStateUIFlow.value = false
|
_pairedStateUIFlow.value = false
|
||||||
unpairing = false
|
unpairing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI flows that must have defined values right
|
||||||
|
// at start are initialized here.
|
||||||
|
|
||||||
|
// The paired state UI flow is special in that it is also
|
||||||
|
// used as the backing store for the isPaired() function,
|
||||||
|
// so setting up that UI state flow equals updating that
|
||||||
|
// paired state.
|
||||||
|
val paired = pumpManager!!.getPairedPumpAddresses().isNotEmpty()
|
||||||
|
_pairedStateUIFlow.value = paired
|
||||||
|
|
||||||
|
setDriverState(DriverState.Disconnected)
|
||||||
|
|
||||||
|
// NOTE: EventInitializationChanged is sent in getPumpStatus() .
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI flows that must have defined values right
|
|
||||||
// at start are initialized here.
|
|
||||||
|
|
||||||
// The paired state UI flow is special in that it is also
|
|
||||||
// used as the backing store for the isPaired() function,
|
|
||||||
// so setting up that UI state flow equals updating that
|
|
||||||
// paired state.
|
|
||||||
val paired = pumpManager!!.getPairedPumpAddresses().isNotEmpty()
|
|
||||||
_pairedStateUIFlow.value = paired
|
|
||||||
|
|
||||||
setDriverState(DriverState.Disconnected)
|
|
||||||
|
|
||||||
// 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 {
|
||||||
// Set maxNumAttempts to null to turn off the connection attempt limit inside the connect() call.
|
runWithPermissionCheck(
|
||||||
// The AAPS queue thread will anyway cause the connectionSetupJob to be canceled when its
|
context, config, aapsLogger, androidPermission,
|
||||||
// connection timeout expires, so the Pump class' own connection attempt limiter is redundant.
|
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
|
||||||
pump?.connect(maxNumAttempts = null)
|
) {
|
||||||
|
// 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
|
||||||
|
// connection timeout expires, so the Pump class' own connection attempt limiter is redundant.
|
||||||
|
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,15 +1509,22 @@ 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()
|
||||||
aapsLogger.info(
|
// since pairing requires Bluetooth permissions.
|
||||||
LTag.PUMP,
|
val pairingResult = runWithPermissionCheck(
|
||||||
"New pairing PIN request from Combo pump with Bluetooth " +
|
context, config, aapsLogger, androidPermission,
|
||||||
"address $newPumpAddress (previous attempt failed: $previousAttemptFailed)"
|
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
|
||||||
)
|
) {
|
||||||
_previousPairingAttemptFailedFlow.value = previousAttemptFailed
|
pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed ->
|
||||||
newPINChannel.receive()
|
aapsLogger.info(
|
||||||
} ?: throw IllegalStateException("Attempting to access uninitialized pump manager")
|
LTag.PUMP,
|
||||||
|
"New pairing PIN request from Combo pump with Bluetooth " +
|
||||||
|
"address $newPumpAddress (previous attempt failed: $previousAttemptFailed)"
|
||||||
|
)
|
||||||
|
_previousPairingAttemptFailedFlow.value = previousAttemptFailed
|
||||||
|
newPINChannel.receive()
|
||||||
|
} ?: throw IllegalStateException("Attempting to access uninitialized pump manager")
|
||||||
|
}
|
||||||
|
|
||||||
if (pairingResult !is ComboCtlPumpManager.PairingResult.Success)
|
if (pairingResult !is ComboCtlPumpManager.PairingResult.Success)
|
||||||
return@async
|
return@async
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue